Skip to content

Add Manacher’s Algorithm for Longest Palindromic Substring #5462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
91 changes: 91 additions & 0 deletions src/main/java/com/thealgorithms/strings/Manacher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.thealgorithms.strings;

/**
* Wikipedia: https://en.wikipedia.org/wiki/Longest_palindromic_substring#Manacher's_algorithm
*/
public final class Manacher {

// Private constructor to prevent instantiation
private Manacher() {
}

/**
* Test code for Manacher's Algorithm
*/
public static void main(String[] args) {
assert longestPalindrome("babad").equals("bab") || longestPalindrome("babad").equals("aba");
assert longestPalindrome("cbbd").equals("bb");
assert longestPalindrome("a").equals("a");
assert longestPalindrome("ac").equals("a") || longestPalindrome("ac").equals("c");
}

/**
* Finds the longest palindromic substring using Manacher's Algorithm
*
* @param s The input string
* @return The longest palindromic substring in {@code s}
*/
public static String longestPalindrome(String s) {
// Preprocess the string to avoid even-length palindrome issues
String processedString = preprocess(s);
int n = processedString.length();
int[] p = new int[n]; // Array to store the radius of palindromes

// Separate variable declarations into individual statements
int center = 0;
int rightBoundary = 0;
int maxLen = 0;
int centerIndex = 0;

// Iterate over the preprocessed string to calculate the palindrome radii
for (int i = 1; i < n - 1; i++) {
// Mirror the current index i to find its corresponding mirrored index
int mirror = 2 * center - i;

// If the current index is within the right boundary, mirror the palindrome radius
if (i < rightBoundary) {
p[i] = Math.min(rightBoundary - i, p[mirror]);
}

// Try to expand the palindrome centered at i
while (processedString.charAt(i + 1 + p[i]) == processedString.charAt(i - 1 - p[i])) {
p[i]++;
}

// Update center and right boundary if palindrome expands beyond current right boundary
if (i + p[i] > rightBoundary) {
center = i;
rightBoundary = i + p[i];
}

// Track the maximum length and center index of the longest palindrome found so far
if (p[i] > maxLen) {
maxLen = p[i];
centerIndex = i;
}
}

// Extract the longest palindrome from the original string
int start = (centerIndex - maxLen) / 2; // Get the starting index in the original string
return s.substring(start, start + maxLen);
}

/**
* Preprocesses the input string by inserting a special character ('#') between each character
* and adding '^' at the start and '$' at the end to avoid boundary conditions.
*
* @param s The original string
* @return The preprocessed string with additional characters
*/
private static String preprocess(String s) {
if (s.isEmpty()) {
return "^$";
}
StringBuilder sb = new StringBuilder("^");
for (char c : s.toCharArray()) {
sb.append('#').append(c);
}
sb.append("#$");
return sb.toString();
}
}
37 changes: 37 additions & 0 deletions src/test/java/com/thealgorithms/strings/ManacherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.thealgorithms.strings;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class ManacherTest {

@Test
public void testLongestPalindrome() {
assertEquals("aabcdefggfedcbaa", Manacher.longestPalindrome("abracadabraabcdefggfedcbaabracadabra")); // Long string with embedded palindrome
assertEquals("racecar", Manacher.longestPalindrome("somelongtextwithracecarmiddletext")); // Longer string with racecar palindrome
assertEquals("ananananananana", Manacher.longestPalindrome("bananananananana")); // Repetitive pattern with palindrome
assertEquals("defgfed", Manacher.longestPalindrome("qwertydefgfedzxcvbnm")); // Palindrome in middle of long string
assertEquals("abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba", Manacher.longestPalindrome("abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcba")); // Symmetrical section
}

@Test
public void testEmptyAndSingle() {
assertEquals("", Manacher.longestPalindrome("")); // Empty string
assertEquals("a", Manacher.longestPalindrome("a")); // Single character
}

@Test
public void testComplexCases() {
assertEquals("tattarrattat", Manacher.longestPalindrome("abcdefghijklmnopqrstuvwxyzttattarrattatabcdefghijklmnopqrstuvwxyz")); // Long palindrome inside a large string
assertEquals("aaaaabaaaaa", Manacher.longestPalindrome("aaaaabaaaaacbaaaaa")); // Large repetitive character set
assertEquals("abcdefghhgfedcba", Manacher.longestPalindrome("sometextrandomabcdefgabcdefghhgfedcbahijklmnopqrstuvwxyz")); // Large string with clear palindromic section
assertEquals("madaminedenimadam", Manacher.longestPalindrome("therewasasignthatsaidmadaminedenimadamitwasthereallalong")); // Famous palindrome within a long string
}

@Test
public void testSentencePalindromes() {
assertEquals("lanacanal", Manacher.longestPalindrome("XThisisalongtextbuthiddeninsideisAmanaplanacanalPanamaWhichweknowisfamous"));
assertEquals("everoddoreve", Manacher.longestPalindrome("AverylongstringthatcontainsNeveroddoreveninahiddenmanner")); // Another sentence-like palindrome
}
}