Skip to content

Commit d33b0ab

Browse files
committed
feat: implement Booth's algorithm for lexicographically minimal rotation of a string.
1 parent 55ff0ad commit d33b0ab

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

String/BoothsAlgorithm.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Booth's Algorithm finds the lexicographically minimal rotation of a string.
3+
* Time Complexity: O(n) - Linear time where n is the length of input string
4+
* Space Complexity: O(n) - Linear space for failure function array
5+
* For More Visit - https://en.wikipedia.org/wiki/Booth%27s_multiplication_algorithm
6+
* @example
7+
* Input: "baca"
8+
* All possible rotations:
9+
* - "baca"
10+
* - "acab"
11+
* - "caba"
12+
* - "abac"
13+
* Output: "abac" (lexicographically smallest)
14+
*
15+
* How it works:
16+
* 1. Doubles the input string to handle all rotations
17+
* 2. Uses failure function (similar to KMP) to find minimal rotation
18+
* 3. Maintains a pointer to the start of minimal rotation found so far
19+
20+
* @param {string} str - Input string to find minimal rotation
21+
* @returns {string} - Lexicographically minimal rotation of the input string
22+
* @throws {Error} - If input is not a string or is empty
23+
*/
24+
export function findMinimalRotation(str) {
25+
if (typeof str !== 'string') {
26+
throw new Error('Input must be a string')
27+
}
28+
29+
if (str.length === 0) {
30+
throw new Error('Input string cannot be empty')
31+
}
32+
33+
// Double the string for rotation comparison
34+
// This allows us to check all rotations by just sliding a window
35+
const s = str + str
36+
const n = s.length
37+
38+
// Initialize failure function array
39+
const f = new Array(n).fill(-1)
40+
let k = 0 // Starting position of minimal rotation
41+
42+
//Algorithm's implementation
43+
// Iterate through the doubled string
44+
// j is the current position we're examining
45+
for (let j = 1; j < n; j++) {
46+
// i is the length of the matched prefix in the current candidate
47+
// Get the failure function value for the previous position
48+
let i = f[j - k - 1]
49+
// This loop handles the case when we need to update our current minimal rotation
50+
// It compares characters and finds if there's a better (lexicographically smaller) rotation
51+
while (i !== -1 && s[j] !== s[k + i + 1]) {
52+
// If we find a smaller character, we've found a better rotation
53+
// Update k to the new starting position
54+
if (s[j] < s[k + i + 1]) {
55+
// j-i-1 gives us the starting position of the new minimal rotation
56+
k = j - i - 1
57+
}
58+
// Update i using the failure function to try shorter prefixes
59+
i = f[i]
60+
}
61+
62+
// This block updates the failure function and handles new character comparisons
63+
if (i === -1 && s[j] !== s[k + i + 1]) {
64+
// If current character is smaller, update the minimal rotation start
65+
if (s[j] < s[k + i + 1]) {
66+
k = j
67+
}
68+
//If no match found,mark failure function accordingly
69+
f[j - k] = -1
70+
} else {
71+
//If match found, extend the matched length
72+
f[j - k] = i + 1
73+
}
74+
}
75+
// After finding k (the starting position of minimal rotation):
76+
// 1. slice(k): Take substring from position k to end
77+
// 2. slice(0, k): Take substring from start to position k
78+
// 3. Concatenate them to get the minimal rotation
79+
return str.slice(k) + str.slice(0, k)
80+
}

String/test/BoothsAlgorithm.test.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { findMinimalRotation } from '../BoothsAlgorithm'
2+
3+
describe('BoothsAlgorithm', () => {
4+
it('should throw an error if input is not a string', () => {
5+
expect(() => findMinimalRotation(null)).toThrow('Input must be a string')
6+
expect(() => findMinimalRotation(undefined)).toThrow(
7+
'Input must be a string'
8+
)
9+
expect(() => findMinimalRotation(123)).toThrow('Input must be a string')
10+
expect(() => findMinimalRotation([])).toThrow('Input must be a string')
11+
})
12+
13+
it('should throw an error if input string is empty', () => {
14+
expect(() => findMinimalRotation('')).toThrow(
15+
'Input string cannot be empty'
16+
)
17+
})
18+
19+
it('should find minimal rotation for simple strings', () => {
20+
expect(findMinimalRotation('abc')).toBe('abc')
21+
expect(findMinimalRotation('bca')).toBe('abc')
22+
expect(findMinimalRotation('cab')).toBe('abc')
23+
})
24+
25+
it('should handle strings with repeated characters', () => {
26+
expect(findMinimalRotation('aaaa')).toBe('aaaa')
27+
expect(findMinimalRotation('aaab')).toBe('aaab')
28+
expect(findMinimalRotation('baaa')).toBe('aaab')
29+
})
30+
31+
it('should handle strings with special characters', () => {
32+
expect(findMinimalRotation('12#$')).toBe('#$12')
33+
expect(findMinimalRotation('@abc')).toBe('@abc')
34+
expect(findMinimalRotation('xyz!')).toBe('!xyz')
35+
})
36+
37+
it('should handle longer strings', () => {
38+
expect(findMinimalRotation('algorithm')).toBe('algorithm')
39+
expect(findMinimalRotation('rithmalgo')).toBe('algorithm')
40+
expect(findMinimalRotation('gorithmal')).toBe('algorithm')
41+
})
42+
43+
it('should be case sensitive', () => {
44+
expect(findMinimalRotation('AbC')).toBe('AbC')
45+
expect(findMinimalRotation('BcA')).toBe('ABc')
46+
expect(findMinimalRotation('CAb')).toBe('AbC')
47+
})
48+
49+
it('should handle palindromes', () => {
50+
expect(findMinimalRotation('radar')).toBe('adarr')
51+
expect(findMinimalRotation('level')).toBe('ellev')
52+
})
53+
})

0 commit comments

Comments
 (0)