Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.thealgorithms.strings;

/**
* Finds the lexicographically smallest cyclic rotation of a string using
* Booth's algorithm.
*
* <p>The algorithm compares candidate rotations without materializing them,
* running in O(n) time and O(1) auxiliary space.</p>
*
* @see <a href="https://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation">
* Lexicographically minimal string rotation</a>
*/
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.
*
* <p>For example, the smallest rotation of {@code "baca"} is
* {@code "abac"}, which starts at index {@code 3}.</p>
*
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading