Skip to content

Commit 1460eb7

Browse files
donutz03donutt03
andauthored
Add Manacher’s Algorithm for Longest Palindromic Substring (#5462)
* Added Manacher Algorithm * Formatted ManacherTest.java * Formatted Manacher.java * Refactor: Update Manacher's algorithm tests and improve readability - Added parameterized tests for longestPalindrome, empty cases, complex cases, and sentence palindromes. - Removed unnecessary comments for cleaner code. - Renamed variable `p` to `palindromeLengths` for clarity based on code review feedback. --------- Co-authored-by: Ionut Hodoroaga <[email protected]>
1 parent 2643ab5 commit 1460eb7

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.thealgorithms.strings;
2+
3+
/**
4+
* Wikipedia: https://en.wikipedia.org/wiki/Longest_palindromic_substring#Manacher's_algorithm
5+
*/
6+
public final class Manacher {
7+
8+
private Manacher() {
9+
}
10+
11+
/**
12+
* Finds the longest palindromic substring using Manacher's Algorithm
13+
*
14+
* @param s The input string
15+
* @return The longest palindromic substring in {@code s}
16+
*/
17+
public static String longestPalindrome(String s) {
18+
final String processedString = preprocess(s);
19+
int[] palindromeLengths = new int[processedString.length()];
20+
int center = 0;
21+
int rightBoundary = 0;
22+
int maxLen = 0;
23+
int centerIndex = 0;
24+
25+
for (int i = 1; i < processedString.length() - 1; i++) {
26+
int mirror = 2 * center - i;
27+
28+
if (i < rightBoundary) {
29+
palindromeLengths[i] = Math.min(rightBoundary - i, palindromeLengths[mirror]);
30+
}
31+
32+
while (processedString.charAt(i + 1 + palindromeLengths[i]) == processedString.charAt(i - 1 - palindromeLengths[i])) {
33+
palindromeLengths[i]++;
34+
}
35+
36+
if (i + palindromeLengths[i] > rightBoundary) {
37+
center = i;
38+
rightBoundary = i + palindromeLengths[i];
39+
}
40+
41+
if (palindromeLengths[i] > maxLen) {
42+
maxLen = palindromeLengths[i];
43+
centerIndex = i;
44+
}
45+
}
46+
47+
final int start = (centerIndex - maxLen) / 2;
48+
return s.substring(start, start + maxLen);
49+
}
50+
51+
/**
52+
* Preprocesses the input string by inserting a special character ('#') between each character
53+
* and adding '^' at the start and '$' at the end to avoid boundary conditions.
54+
*
55+
* @param s The original string
56+
* @return The preprocessed string with additional characters
57+
*/
58+
private static String preprocess(String s) {
59+
if (s.isEmpty()) {
60+
return "^$";
61+
}
62+
StringBuilder sb = new StringBuilder("^");
63+
for (char c : s.toCharArray()) {
64+
sb.append('#').append(c);
65+
}
66+
sb.append("#$");
67+
return sb.toString();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.stream.Stream;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.Arguments;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
10+
public class ManacherTest {
11+
12+
@ParameterizedTest
13+
@MethodSource("provideTestCasesForLongestPalindrome")
14+
public void testLongestPalindrome(String input, String expected) {
15+
assertEquals(expected, Manacher.longestPalindrome(input));
16+
}
17+
18+
private static Stream<Arguments> provideTestCasesForLongestPalindrome() {
19+
return Stream.of(Arguments.of("abracadabraabcdefggfedcbaabracadabra", "aabcdefggfedcbaa"), Arguments.of("somelongtextwithracecarmiddletext", "racecar"), Arguments.of("bananananananana", "ananananananana"), Arguments.of("qwertydefgfedzxcvbnm", "defgfed"),
20+
Arguments.of("abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba", "abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba"));
21+
}
22+
23+
@ParameterizedTest
24+
@MethodSource("provideTestCasesForEmptyAndSingle")
25+
public void testEmptyAndSingle(String input, String expected) {
26+
assertEquals(expected, Manacher.longestPalindrome(input));
27+
}
28+
29+
private static Stream<Arguments> provideTestCasesForEmptyAndSingle() {
30+
return Stream.of(Arguments.of("", ""), Arguments.of("a", "a"));
31+
}
32+
33+
@ParameterizedTest
34+
@MethodSource("provideTestCasesForComplexCases")
35+
public void testComplexCases(String input, String expected) {
36+
assertEquals(expected, Manacher.longestPalindrome(input));
37+
}
38+
39+
private static Stream<Arguments> provideTestCasesForComplexCases() {
40+
return Stream.of(Arguments.of("abcdefghijklmnopqrstuvwxyzttattarrattatabcdefghijklmnopqrstuvwxyz", "tattarrattat"), Arguments.of("aaaaabaaaaacbaaaaa", "aaaaabaaaaa"), Arguments.of("sometextrandomabcdefgabcdefghhgfedcbahijklmnopqrstuvwxyz", "abcdefghhgfedcba"),
41+
Arguments.of("therewasasignthatsaidmadaminedenimadamitwasthereallalong", "madaminedenimadam"));
42+
}
43+
44+
@ParameterizedTest
45+
@MethodSource("provideTestCasesForSentencePalindromes")
46+
public void testSentencePalindromes(String input, String expected) {
47+
assertEquals(expected, Manacher.longestPalindrome(input));
48+
}
49+
50+
private static Stream<Arguments> provideTestCasesForSentencePalindromes() {
51+
return Stream.of(Arguments.of("XThisisalongtextbuthiddeninsideisAmanaplanacanalPanamaWhichweknowisfamous", "lanacanal"), Arguments.of("AverylongstringthatcontainsNeveroddoreveninahiddenmanner", "everoddoreve"));
52+
}
53+
}

0 commit comments

Comments
 (0)