Skip to content

Commit 09661a2

Browse files
committed
Added Manacher Algorithm
1 parent b849cbb commit 09661a2

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 constructor to prevent instantiation
9+
private Manacher() {
10+
}
11+
12+
/**
13+
* Test code for Manacher's Algorithm
14+
*/
15+
public static void main(String[] args) {
16+
assert longestPalindrome("babad").equals("bab") || longestPalindrome("babad").equals("aba");
17+
assert longestPalindrome("cbbd").equals("bb");
18+
assert longestPalindrome("a").equals("a");
19+
assert longestPalindrome("ac").equals("a") || longestPalindrome("ac").equals("c");
20+
}
21+
22+
/**
23+
* Finds the longest palindromic substring using Manacher's Algorithm
24+
*
25+
* @param s The input string
26+
* @return The longest palindromic substring in {@code s}
27+
*/
28+
public static String longestPalindrome(String s) {
29+
// Preprocess the string to avoid even-length palindrome issues
30+
String processedString = preprocess(s);
31+
int n = processedString.length();
32+
int[] P = new int[n]; // Array to store the radius of palindromes
33+
int center = 0, rightBoundary = 0; // Current center and right boundary of the palindrome
34+
int maxLen = 0, centerIndex = 0; // To track the longest palindrome
35+
36+
// Iterate over the preprocessed string to calculate the palindrome radii
37+
for (int i = 1; i < n - 1; i++) {
38+
// Mirror the current index i to find its corresponding mirrored index
39+
int mirror = 2 * center - i;
40+
41+
// If the current index is within the right boundary, mirror the palindrome radius
42+
if (i < rightBoundary) {
43+
P[i] = Math.min(rightBoundary - i, P[mirror]);
44+
}
45+
46+
// Try to expand the palindrome centered at i
47+
while (processedString.charAt(i + 1 + P[i]) == processedString.charAt(i - 1 - P[i])) {
48+
P[i]++;
49+
}
50+
51+
// Update center and right boundary if palindrome expands beyond current right boundary
52+
if (i + P[i] > rightBoundary) {
53+
center = i;
54+
rightBoundary = i + P[i];
55+
}
56+
57+
// Track the maximum length and center index of the longest palindrome found so far
58+
if (P[i] > maxLen) {
59+
maxLen = P[i];
60+
centerIndex = i;
61+
}
62+
}
63+
64+
// Extract the longest palindrome from the original string
65+
int start = (centerIndex - maxLen) / 2; // Get the starting index in the original string
66+
return s.substring(start, start + maxLen);
67+
}
68+
69+
/**
70+
* Preprocesses the input string by inserting a special character ('#') between each character
71+
* and adding '^' at the start and '$' at the end to avoid boundary conditions.
72+
*
73+
* @param s The original string
74+
* @return The preprocessed string with additional characters
75+
*/
76+
private static String preprocess(String s) {
77+
if (s.isEmpty()) {
78+
return "^$";
79+
}
80+
StringBuilder sb = new StringBuilder("^");
81+
for (char c : s.toCharArray()) {
82+
sb.append('#').append(c);
83+
}
84+
sb.append("#$");
85+
return sb.toString();
86+
}
87+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import org.junit.jupiter.api.Test;
5+
6+
public class ManacherTest {
7+
8+
@Test
9+
public void testLongestPalindrome() {
10+
assertEquals("aabcdefggfedcbaa", Manacher.longestPalindrome("abracadabraabcdefggfedcbaabracadabra")); // Long string with embedded palindrome
11+
assertEquals("racecar", Manacher.longestPalindrome("somelongtextwithracecarmiddletext")); // Longer string with racecar palindrome
12+
assertEquals("ananananananana", Manacher.longestPalindrome("bananananananana")); // Repetitive pattern with palindrome
13+
assertEquals("defgfed", Manacher.longestPalindrome("qwertydefgfedzxcvbnm")); // Palindrome in middle of long string
14+
assertEquals("abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba", Manacher.longestPalindrome("abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba")); // Symmetrical section
15+
}
16+
17+
@Test
18+
public void testEmptyAndSingle() {
19+
assertEquals("", Manacher.longestPalindrome("")); // Empty string
20+
assertEquals("a", Manacher.longestPalindrome("a")); // Single character
21+
}
22+
23+
@Test
24+
public void testComplexCases() {
25+
assertEquals("tattarrattat", Manacher.longestPalindrome("abcdefghijklmnopqrstuvwxyzttattarrattatabcdefghijklmnopqrstuvwxyz")); // Long palindrome inside a large string
26+
assertEquals("aaaaabaaaaa", Manacher.longestPalindrome("aaaaabaaaaacbaaaaa")); // Large repetitive character set
27+
assertEquals("abcdefghhgfedcba", Manacher.longestPalindrome("sometextrandomabcdefgabcdefghhgfedcbahijklmnopqrstuvwxyz")); // Large string with clear palindromic section
28+
assertEquals("madaminedenimadam", Manacher.longestPalindrome("therewasasignthatsaidmadaminedenimadamitwasthereallalong")); // Famous palindrome within a long string
29+
}
30+
31+
@Test
32+
public void testSentencePalindromes() {
33+
assertEquals("lanacanal", Manacher.longestPalindrome("XThisisalongtextbuthiddeninsideisAmanaplanacanalPanamaWhichweknowisfamous"));
34+
assertEquals("everoddoreve", Manacher.longestPalindrome("AverylongstringthatcontainsNeveroddoreveninahiddenmanner")); // Another sentence-like palindrome
35+
}
36+
}

0 commit comments

Comments
 (0)