diff --git a/src/main/java/com/thealgorithms/strings/LyndonFactorization.java b/src/main/java/com/thealgorithms/strings/LyndonFactorization.java new file mode 100644 index 000000000000..3eb13b00d0fb --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LyndonFactorization.java @@ -0,0 +1,61 @@ +package com.thealgorithms.strings; + +import java.util.ArrayList; +import java.util.List; + +/** + * Factorizes a string into its Lyndon words using Duval's algorithm. + * + *

Duval's algorithm runs in O(n) time and uses O(1) auxiliary working + * space, excluding the returned list and its strings. + * + *

Reference: + * https://cp-algorithms.com/string/lyndon_factorization.html + */ +public final class LyndonFactorization { + private LyndonFactorization() { + } + + /** + * Returns the standard Lyndon factorization of {@code text}. + * + *

The returned factors are in non-increasing lexicographic order + * according to Java's UTF-16 code-unit ordering. + * + * @param text input string to factorize + * @return Lyndon factors in non-increasing lexicographic order + * @throws IllegalArgumentException if {@code text} is null + */ + public static List factorize(String text) { + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + + List factors = new ArrayList<>(); + int n = text.length(); + int start = 0; + + while (start < n) { + int candidate = start + 1; + int match = start; + + while (candidate < n && text.charAt(match) <= text.charAt(candidate)) { + if (text.charAt(match) < text.charAt(candidate)) { + match = start; + } else { + match++; + } + candidate++; + } + + int length = candidate - match; + + while (start <= match) { + factors.add(text.substring(start, start + length)); + start += length; + } + } + + return factors; + } +} diff --git a/src/test/java/com/thealgorithms/strings/LyndonFactorizationTest.java b/src/test/java/com/thealgorithms/strings/LyndonFactorizationTest.java new file mode 100644 index 000000000000..4ecb86e2a99c --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LyndonFactorizationTest.java @@ -0,0 +1,35 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class LyndonFactorizationTest { + + @Test + void factorizesBanana() { + assertEquals(List.of("b", "an", "an", "a"), LyndonFactorization.factorize("banana")); + } + + @Test + void factorizesRepeatedPattern() { + assertEquals(List.of("ab", "ab", "ab"), LyndonFactorization.factorize("ababab")); + } + + @Test + void factorizesSingleCharacter() { + assertEquals(List.of("a"), LyndonFactorization.factorize("a")); + } + + @Test + void factorizesEmptyString() { + assertEquals(List.of(), LyndonFactorization.factorize("")); + } + + @Test + void rejectsNullInput() { + assertThrows(IllegalArgumentException.class, () -> LyndonFactorization.factorize(null)); + } +}