diff --git a/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java b/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java new file mode 100644 index 000000000000..01c2f67a8569 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java @@ -0,0 +1,69 @@ +package com.thealgorithms.strings; + +/** + * Finds the lexicographically smallest cyclic rotation of a string using + * Booth's algorithm. + * + *
The algorithm compares candidate rotations without materializing them, + * running in O(n) time and O(1) auxiliary space.
+ * + * @see + * Lexicographically minimal string rotation + */ +public final class BoothsAlgorithm { + private BoothsAlgorithm() { + } + + /** + * Returns the starting index of the lexicographically smallest cyclic + * rotation of {@code input}. If several rotations are equal and minimal, + * the algorithm returns the smallest starting index among them. + * + *For example, the smallest rotation of {@code "baca"} is + * {@code "abac"}, which starts at index {@code 3}.
+ * + * @param input the string whose minimal cyclic rotation is sought + * @return the starting index of the minimal rotation, or {@code 0} for an + * empty string + * @throws IllegalArgumentException if {@code input} is {@code null} + */ + public static int minimalRotationIndex(String input) { + if (input == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + + int length = input.length(); + if (length == 0) { + return 0; + } + + int first = 0; + int second = 1; + int offset = 0; + + while (first < length && second < length && offset < length) { + char firstChar = input.charAt((first + offset) % length); + char secondChar = input.charAt((second + offset) % length); + + if (firstChar == secondChar) { + offset++; + continue; + } + + if (firstChar > secondChar) { + first += offset + 1; + if (first <= second) { + first = second + 1; + } + } else { + second += offset + 1; + if (second <= first) { + second = first + 1; + } + } + offset = 0; + } + + return Math.min(first, second); + } +} diff --git a/src/test/java/com/thealgorithms/strings/BoothsAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/BoothsAlgorithmTest.java new file mode 100644 index 000000000000..19ff58eb0988 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/BoothsAlgorithmTest.java @@ -0,0 +1,45 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class BoothsAlgorithmTest { + + @Test + public void testMinimalRotationIndexForExample() { + assertEquals(3, BoothsAlgorithm.minimalRotationIndex("baca")); + } + + @Test + public void testEmptyString() { + assertEquals(0, BoothsAlgorithm.minimalRotationIndex("")); + } + + @Test + public void testSingleCharacter() { + assertEquals(0, BoothsAlgorithm.minimalRotationIndex("x")); + } + + @Test + public void testRepeatedCharacters() { + assertEquals(0, BoothsAlgorithm.minimalRotationIndex("aaaaa")); + } + + @Test + public void testPeriodicString() { + assertEquals(1, BoothsAlgorithm.minimalRotationIndex("baba")); + assertEquals(1, BoothsAlgorithm.minimalRotationIndex("cabcab")); + } + + @Test + public void testStringAlreadyHasMinimalRotationAtStart() { + assertEquals(0, BoothsAlgorithm.minimalRotationIndex("abc")); + } + + @Test + public void testNullInput() { + assertThrows(IllegalArgumentException.class, () -> BoothsAlgorithm.minimalRotationIndex(null)); + } +}