Skip to content

Commit 2dd1a0a

Browse files
authored
Add Booth's algorithm for minimal string rotation (#7644)
* Add Booth's algorithm for minimal string rotation * Add tests for Booth's minimal rotation algorithm * Use constant-space comparisons in Booth's algorithm
1 parent 862de93 commit 2dd1a0a

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.thealgorithms.strings;
2+
3+
/**
4+
* Finds the lexicographically smallest cyclic rotation of a string using
5+
* Booth's algorithm.
6+
*
7+
* <p>The algorithm compares candidate rotations without materializing them,
8+
* running in O(n) time and O(1) auxiliary space.</p>
9+
*
10+
* @see <a href="https://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation">
11+
* Lexicographically minimal string rotation</a>
12+
*/
13+
public final class BoothsAlgorithm {
14+
private BoothsAlgorithm() {
15+
}
16+
17+
/**
18+
* Returns the starting index of the lexicographically smallest cyclic
19+
* rotation of {@code input}. If several rotations are equal and minimal,
20+
* the algorithm returns the smallest starting index among them.
21+
*
22+
* <p>For example, the smallest rotation of {@code "baca"} is
23+
* {@code "abac"}, which starts at index {@code 3}.</p>
24+
*
25+
* @param input the string whose minimal cyclic rotation is sought
26+
* @return the starting index of the minimal rotation, or {@code 0} for an
27+
* empty string
28+
* @throws IllegalArgumentException if {@code input} is {@code null}
29+
*/
30+
public static int minimalRotationIndex(String input) {
31+
if (input == null) {
32+
throw new IllegalArgumentException("Input string must not be null");
33+
}
34+
35+
int length = input.length();
36+
if (length == 0) {
37+
return 0;
38+
}
39+
40+
int first = 0;
41+
int second = 1;
42+
int offset = 0;
43+
44+
while (first < length && second < length && offset < length) {
45+
char firstChar = input.charAt((first + offset) % length);
46+
char secondChar = input.charAt((second + offset) % length);
47+
48+
if (firstChar == secondChar) {
49+
offset++;
50+
continue;
51+
}
52+
53+
if (firstChar > secondChar) {
54+
first += offset + 1;
55+
if (first <= second) {
56+
first = second + 1;
57+
}
58+
} else {
59+
second += offset + 1;
60+
if (second <= first) {
61+
second = first + 1;
62+
}
63+
}
64+
offset = 0;
65+
}
66+
67+
return Math.min(first, second);
68+
}
69+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
public class BoothsAlgorithmTest {
9+
10+
@Test
11+
public void testMinimalRotationIndexForExample() {
12+
assertEquals(3, BoothsAlgorithm.minimalRotationIndex("baca"));
13+
}
14+
15+
@Test
16+
public void testEmptyString() {
17+
assertEquals(0, BoothsAlgorithm.minimalRotationIndex(""));
18+
}
19+
20+
@Test
21+
public void testSingleCharacter() {
22+
assertEquals(0, BoothsAlgorithm.minimalRotationIndex("x"));
23+
}
24+
25+
@Test
26+
public void testRepeatedCharacters() {
27+
assertEquals(0, BoothsAlgorithm.minimalRotationIndex("aaaaa"));
28+
}
29+
30+
@Test
31+
public void testPeriodicString() {
32+
assertEquals(1, BoothsAlgorithm.minimalRotationIndex("baba"));
33+
assertEquals(1, BoothsAlgorithm.minimalRotationIndex("cabcab"));
34+
}
35+
36+
@Test
37+
public void testStringAlreadyHasMinimalRotationAtStart() {
38+
assertEquals(0, BoothsAlgorithm.minimalRotationIndex("abc"));
39+
}
40+
41+
@Test
42+
public void testNullInput() {
43+
assertThrows(IllegalArgumentException.class, () -> BoothsAlgorithm.minimalRotationIndex(null));
44+
}
45+
}

0 commit comments

Comments
 (0)