Facebook Pixel

1960. Maximum Product of the Length of Two Palindromic Substrings

HardStringHash FunctionRolling Hash
Leetcode Link

Problem Description

You are given a string s (0-indexed) and need to find two non-overlapping palindromic substrings, both with odd lengths, such that the product of their lengths is as large as possible.

The requirements are:

  • Find two substrings s[i...j] and s[k...l] where the indices satisfy 0 <= i <= j < k <= l < s.length
  • Both substrings must be palindromes (read the same forwards and backwards)
  • Both substrings must have odd lengths
  • The substrings cannot overlap (they are non-intersecting since j < k)
  • Maximize the product of the two substring lengths

For example, if you have a string where you can find a palindrome of length 5 and another non-overlapping palindrome of length 3, their product would be 15.

The solution uses Manacher's algorithm to efficiently find all odd-length palindromes. It maintains:

  • hlen[i]: the half-length of the longest odd palindrome centered at index i
  • prefix[i]: the maximum odd palindrome length ending at or before index i
  • suffix[i]: the maximum odd palindrome length starting at or after index i

The algorithm first computes all palindrome lengths using the expand-around-center technique with optimization. Then it builds prefix and suffix arrays to track the maximum palindrome lengths up to and from each position. Finally, it tries all possible split points to find two non-overlapping palindromes with maximum product.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to find the best way to split the string into two parts where each part contains the longest possible odd-length palindrome. Since the palindromes cannot overlap, we can think of this as choosing a split point in the string.

First, we need to efficiently find all odd-length palindromes. Since we only care about odd-length palindromes, each one has a center character. For a character at position i, we can expand outward to find the longest palindrome centered at i. However, doing this naively for each position would be expensive.

This is where Manacher's algorithm comes in - it uses previously computed palindrome information to avoid redundant checks. If we've already found a long palindrome, and we're checking a position inside it, we can use symmetry to get a head start on our expansion.

Once we know all palindromes, the next challenge is: how do we efficiently find the best pair? We can't check all possible pairs as that would be too slow.

The breakthrough is realizing that for any split point i, we want:

  • The longest palindrome that ends before position i (in the left part)
  • The longest palindrome that starts from position i or after (in the right part)

This leads us to precompute two arrays:

  • prefix[i]: maximum odd palindrome length in s[0...i]
  • suffix[i]: maximum odd palindrome length in s[i...n-1]

But there's a subtle detail - a palindrome centered at position j with half-length h spans from j-h to j+h. So we need to carefully track where palindromes end and begin when building these arrays.

The final trick is handling the propagation of palindrome lengths. If there's a palindrome of length L ending at position i, there's also a palindrome of length L-2 ending at position i-1 (by removing one character from each end). This property helps us fill in the prefix and suffix arrays completely.

With these arrays ready, we simply try each split point and multiply the best palindrome length on the left with the best on the right, keeping track of the maximum product.

Solution Approach

The implementation consists of four main phases:

Phase 1: Find all odd-length palindromes using Manacher's Algorithm

The algorithm maintains hlen[i] which stores the half-length of the longest odd palindrome centered at index i. For example, if hlen[i] = 2, the palindrome spans from i-2 to i+2 with total length 5.

center = right = 0
for i in range(n):
    if i < right:
        hlen[i] = min(right - i, hlen[2 * center - i])

This optimization uses symmetry: if position i is within a known palindrome (centered at center with right boundary at right), we can initialize hlen[i] based on its mirror position 2 * center - i.

Then we expand around center i:

while (0 <= i - 1 - hlen[i] and i + 1 + hlen[i] < len(s) 
       and s[i - 1 - hlen[i]] == s[i + 1 + hlen[i]]):
    hlen[i] += 1

Phase 2: Initialize prefix and suffix arrays

For each palindrome centered at i with half-length hlen[i]:

  • It ends at position i + hlen[i], so we update prefix[i + hlen[i]]
  • It starts at position i - hlen[i], so we update suffix[i - hlen[i]]
  • The palindrome length is 2 * hlen[i] + 1
prefix[i + hlen[i]] = max(prefix[i + hlen[i]], 2 * hlen[i] + 1)
suffix[i - hlen[i]] = max(suffix[i - hlen[i]], 2 * hlen[i] + 1)

Phase 3: Propagate palindrome lengths

If a palindrome of length L ends at position j, then a palindrome of length L-2 ends at position j-1 (by removing one character from each end).

for i in range(1, n):
    prefix[~i] = max(prefix[~i], prefix[~i + 1] - 2)
    suffix[i] = max(suffix[i], suffix[i - 1] - 2)

The ~i notation means -(i+1), so we're iterating backwards through prefix array while iterating forwards through suffix array.

Phase 4: Compute cumulative maximums

Transform the arrays so that:

  • prefix[i] = maximum palindrome length in range [0, i]
  • suffix[i] = maximum palindrome length in range [i, n-1]
for i in range(1, n):
    prefix[i] = max(prefix[i - 1], prefix[i])
    suffix[~i] = max(suffix[~i], suffix[~i + 1])

Phase 5: Find maximum product

For each possible split point i, multiply the best palindrome on the left (prefix[i-1]) with the best palindrome on the right (suffix[i]):

return max(prefix[i - 1] * suffix[i] for i in range(1, n))

This ensures the two palindromes don't overlap since one ends before position i and the other starts at or after position i.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with the string s = "ababbb".

Phase 1: Find all odd-length palindromes

We calculate hlen[i] for each position:

  • Position 0 ('a'): Expand around 'a' → Just 'a' itself → hlen[0] = 0
  • Position 1 ('b'): Expand around 'b' → 'aba' works → hlen[1] = 1
  • Position 2 ('a'): Expand around 'a' → 'bab' works → hlen[2] = 1
  • Position 3 ('b'): Expand around 'b' → 'ababb' doesn't work, just 'b' → hlen[3] = 0
  • Position 4 ('b'): Expand around 'b' → 'bbb' works → hlen[4] = 1
  • Position 5 ('b'): Expand around 'b' → Just 'b' itself → hlen[5] = 0

So hlen = [0, 1, 1, 0, 1, 0]

Phase 2: Initialize prefix and suffix arrays

For each palindrome, mark where it ends (prefix) and starts (suffix):

  • i=0: palindrome 'a' (length 1) ends at 0, starts at 0
    • prefix[0] = 1, suffix[0] = 1
  • i=1: palindrome 'aba' (length 3) ends at 2, starts at 0
    • prefix[2] = 3, suffix[0] = max(1, 3) = 3
  • i=2: palindrome 'bab' (length 3) ends at 3, starts at 1
    • prefix[3] = 3, suffix[1] = 3
  • i=3: palindrome 'b' (length 1) ends at 3, starts at 3
    • prefix[3] = max(3, 1) = 3, suffix[3] = 1
  • i=4: palindrome 'bbb' (length 3) ends at 5, starts at 3
    • prefix[5] = 3, suffix[3] = max(1, 3) = 3
  • i=5: palindrome 'b' (length 1) ends at 5, starts at 5
    • prefix[5] = max(3, 1) = 3, suffix[5] = 1

Now: prefix = [1, 0, 3, 3, 0, 3], suffix = [3, 3, 0, 3, 0, 1]

Phase 3: Propagate palindrome lengths

If length L palindrome ends at j, then length L-2 ends at j-1:

  • Working backwards in prefix:
    • prefix[4] = max(0, 3-2) = 1
    • prefix[3] = max(3, 1-2) = 3
    • prefix[2] = max(3, 3-2) = 3
    • prefix[1] = max(0, 3-2) = 1
    • prefix[0] = max(1, 1-2) = 1
  • Working forwards in suffix:
    • suffix[1] = max(3, 3-2) = 3
    • suffix[2] = max(0, 3-2) = 1
    • suffix[3] = max(3, 1-2) = 3
    • suffix[4] = max(0, 3-2) = 1
    • suffix[5] = max(1, 1-2) = 1

Now: prefix = [1, 1, 3, 3, 1, 3], suffix = [3, 3, 1, 3, 1, 1]

Phase 4: Compute cumulative maximums

Transform to cumulative maximum arrays:

  • Prefix (max so far): [1, 1, 3, 3, 3, 3]
  • Suffix (max from here): [3, 3, 3, 3, 1, 1]

Phase 5: Find maximum product

Try each split point:

  • Split at 1: prefix[0] * suffix[1] = 1 * 3 = 3
  • Split at 2: prefix[1] * suffix[2] = 1 * 3 = 3
  • Split at 3: prefix[2] * suffix[3] = 3 * 3 = 9
  • Split at 4: prefix[3] * suffix[4] = 3 * 1 = 3
  • Split at 5: prefix[4] * suffix[5] = 3 * 1 = 3

Maximum product is 9, achieved by splitting at position 3:

  • Left part "aba" contains palindrome "aba" (length 3)
  • Right part "bbb" contains palindrome "bbb" (length 3)
  • Product: 3 × 3 = 9

Solution Implementation

1class Solution:
2    def maxProduct(self, s: str) -> int:
3        n = len(s)
4      
5        # Step 1: Use Manacher's algorithm to find all odd-length palindromes
6        # half_lengths[i] represents half-length of palindrome centered at i
7        half_lengths = [0] * n
8        center = right_boundary = 0
9      
10        for i in range(n):
11            # Use previously computed palindromes to initialize current position
12            if i < right_boundary:
13                mirror_idx = 2 * center - i
14                half_lengths[i] = min(right_boundary - i, half_lengths[mirror_idx])
15          
16            # Expand palindrome centered at i
17            while (0 <= i - 1 - half_lengths[i] and 
18                   i + 1 + half_lengths[i] < n and 
19                   s[i - 1 - half_lengths[i]] == s[i + 1 + half_lengths[i]]):
20                half_lengths[i] += 1
21          
22            # Update center and right boundary if current palindrome extends further
23            if right_boundary < i + half_lengths[i]:
24                center = i
25                right_boundary = i + half_lengths[i]
26      
27        # Step 2: Build prefix and suffix arrays
28        # prefix[i] = length of longest palindrome ending at or before index i
29        # suffix[i] = length of longest palindrome starting at or after index i
30        prefix = [0] * n
31        suffix = [0] * n
32      
33        # Initialize with palindromes at their exact boundaries
34        for i in range(n):
35            right_end = i + half_lengths[i]
36            left_start = i - half_lengths[i]
37            palindrome_length = 2 * half_lengths[i] + 1
38          
39            prefix[right_end] = max(prefix[right_end], palindrome_length)
40            suffix[left_start] = max(suffix[left_start], palindrome_length)
41      
42        # Step 3: Propagate palindrome lengths inward (shrinking by 2 each time)
43        # A palindrome of length L contains palindromes of length L-2, L-4, etc.
44        for i in range(1, n):
45            # Process from right to left for prefix array
46            idx = n - 1 - i  # Using ~i equivalent: ~i = -i - 1
47            prefix[idx] = max(prefix[idx], prefix[idx + 1] - 2)
48            # Process from left to right for suffix array
49            suffix[i] = max(suffix[i], suffix[i - 1] - 2)
50      
51        # Step 4: Calculate cumulative maximum for prefix and suffix arrays
52        for i in range(1, n):
53            # prefix[i] should be max of all palindromes ending at or before i
54            prefix[i] = max(prefix[i - 1], prefix[i])
55            # suffix[i] should be max of all palindromes starting at or after i
56            idx = n - 1 - i  # Using ~i equivalent
57            suffix[idx] = max(suffix[idx], suffix[idx + 1])
58      
59        # Step 5: Find maximum product by trying all split points
60        max_product = 0
61        for split_point in range(1, n):
62            # Product of best palindrome before split and best palindrome after split
63            product = prefix[split_point - 1] * suffix[split_point]
64            max_product = max(max_product, product)
65      
66        return max_product
67
1class Solution {
2    /**
3     * Finds the maximum product of lengths of two non-overlapping palindromic substrings.
4     * @param s the input string
5     * @return the maximum product of two palindrome lengths
6     */
7    public long maxProduct(String s) {
8        int n = s.length();
9      
10        // Base case: if string has only 2 characters, each palindrome has length 1
11        if (n == 2) {
12            return 1;
13        }
14      
15        // Get palindrome lengths centered at each position using Manacher's algorithm
16        int[] palindromeLengths = manachers(s);
17      
18        // Calculate maximum palindrome length ending at or before each position
19        long[] maxPalindromeEndingAt = new long[n];
20        int currentMaxLength = 1;
21        maxPalindromeEndingAt[0] = currentMaxLength;
22      
23        for (int i = 1; i <= n - 1; i++) {
24            // Check if we can extend the maximum palindrome length
25            // Center of palindrome is at midpoint between (i - currentMaxLength - 1) and i
26            int center = (i - currentMaxLength - 1 + i) / 2;
27            if (palindromeLengths[center] > currentMaxLength) {
28                currentMaxLength += 2;
29            }
30            maxPalindromeEndingAt[i] = currentMaxLength;
31        }
32      
33        // Calculate maximum palindrome length starting at or after each position
34        long[] maxPalindromeStartingAt = new long[n];
35        currentMaxLength = 1;
36        maxPalindromeStartingAt[n - 1] = currentMaxLength;
37      
38        for (int i = n - 2; i >= 0; i--) {
39            // Check if we can extend the maximum palindrome length
40            // Center of palindrome is at midpoint between i and (i + currentMaxLength + 1)
41            int center = (i + currentMaxLength + 1 + i) / 2;
42            if (palindromeLengths[center] > currentMaxLength) {
43                currentMaxLength += 2;
44            }
45            maxPalindromeStartingAt[i] = currentMaxLength;
46        }
47      
48        // Find maximum product by trying all split points
49        long maxProduct = 1;
50        for (int splitPoint = 1; splitPoint < n; splitPoint++) {
51            long product = maxPalindromeEndingAt[splitPoint - 1] * maxPalindromeStartingAt[splitPoint];
52            maxProduct = Math.max(maxProduct, product);
53        }
54      
55        return maxProduct;
56    }
57  
58    /**
59     * Implements Manacher's algorithm to find palindrome lengths centered at each position.
60     * @param s the input string
61     * @return array where P[i] represents the length of palindrome centered at position i
62     */
63    private int[] manachers(String s) {
64        int length = s.length();
65        int[] palindromeRadii = new int[length];
66        int centerPosition = 0;  // Center of the rightmost palindrome
67        int rightBoundary = 0;   // Right boundary of the rightmost palindrome
68      
69        for (int i = 0; i < length; i++) {
70            // Mirror position of i with respect to centerPosition
71            int mirrorPosition = (2 * centerPosition) - i;
72          
73            // Use previously computed information if within bounds
74            if (i < rightBoundary) {
75                palindromeRadii[i] = Math.min(rightBoundary - i, palindromeRadii[mirrorPosition]);
76            }
77          
78            // Try to expand palindrome centered at i
79            int rightIndex = i + (1 + palindromeRadii[i]);
80            int leftIndex = i - (1 + palindromeRadii[i]);
81          
82            while (rightIndex < length && leftIndex >= 0 && 
83                   s.charAt(rightIndex) == s.charAt(leftIndex)) {
84                palindromeRadii[i]++;
85                rightIndex++;
86                leftIndex--;
87            }
88          
89            // Update rightmost palindrome if current one extends further
90            if (i + palindromeRadii[i] > rightBoundary) {
91                centerPosition = i;
92                rightBoundary = i + palindromeRadii[i];
93            }
94        }
95      
96        // Convert radius to actual palindrome length (1 + 2 * radius)
97        for (int i = 0; i < length; i++) {
98            palindromeRadii[i] = 1 + 2 * palindromeRadii[i];
99        }
100      
101        return palindromeRadii;
102    }
103}
104
1class Solution {
2public:
3    long long maxProduct(string s) {
4        int n = s.size();
5        long long maxResult = 0;
6        long long maxLeftPalindromeLength = 0;
7      
8        // Array to store the radius of palindrome centered at each position
9        vector<int> palindromeRadius(n);
10        // Array to store the maximum palindrome length from position i to end
11        vector<int> maxRightPalindromeLength(n);
12      
13        // Manacher's algorithm to find all palindrome centers and their radii
14        // This finds odd-length palindromes centered at each position
15        for (int i = 0, left = 0, right = -1; i < n; ++i) {
16            // Initialize k based on whether we can use previously computed information
17            int k = (i > right) ? 1 : min(palindromeRadius[left + right - i], right - i + 1);
18          
19            // Expand palindrome centered at i
20            while (i - k >= 0 && i + k < n && s[i - k] == s[i + k]) {
21                k++;
22            }
23          
24            // Store the radius (k-1 because we incremented one extra time)
25            palindromeRadius[i] = k - 1;
26          
27            // Update the rightmost palindrome boundary if needed
28            if (i + palindromeRadius[i] > right) {
29                left = i - palindromeRadius[i];
30                right = i + palindromeRadius[i];
31            }
32        }
33      
34        // Queue to maintain palindromes for calculating maximum right lengths
35        queue<array<int, 2>> rightQueue;
36      
37        // Calculate maximum palindrome length from each position to the end
38        for (int i = n - 1; i >= 0; --i) {
39            // Remove palindromes that don't extend to current position
40            while (!rightQueue.empty() && 
41                   rightQueue.front()[0] - rightQueue.front()[1] > i - 1) {
42                rightQueue.pop();
43            }
44          
45            // Calculate maximum palindrome length from position i to end
46            // If queue is empty, only the character itself forms a palindrome (length 1)
47            // Otherwise, calculate based on the furthest reaching palindrome
48            maxRightPalindromeLength[i] = 1;
49            if (!rightQueue.empty()) {
50                maxRightPalindromeLength[i] += (rightQueue.front()[0] - i) * 2;
51            }
52          
53            // Add current position and its palindrome radius to queue
54            rightQueue.push({i, palindromeRadius[i]});
55        }
56      
57        // Queue to maintain palindromes for calculating maximum left lengths
58        queue<array<int, 2>> leftQueue;
59      
60        // Calculate maximum palindrome length from start to each position
61        // and compute the maximum product
62        for (int i = 0; i < n - 1; i++) {
63            // Remove palindromes that don't extend to current position
64            while (!leftQueue.empty() && 
65                   leftQueue.front()[0] + leftQueue.front()[1] < i + 1) {
66                leftQueue.pop();
67            }
68          
69            // Calculate maximum palindrome length from start to position i
70            // If queue is empty, only the character itself forms a palindrome (length 1)
71            // Otherwise, calculate based on the furthest reaching palindrome
72            long long currentLeftLength = 1;
73            if (!leftQueue.empty()) {
74                currentLeftLength += (i - leftQueue.front()[0]) * 2;
75            }
76          
77            // Update the maximum left palindrome length seen so far
78            maxLeftPalindromeLength = max(maxLeftPalindromeLength, currentLeftLength);
79          
80            // Calculate product of left and right palindrome lengths
81            // Left: from start to position i, Right: from position i+1 to end
82            maxResult = max(maxResult, 
83                           maxLeftPalindromeLength * maxRightPalindromeLength[i + 1]);
84          
85            // Add current position and its palindrome radius to queue
86            leftQueue.push({i, palindromeRadius[i]});
87        }
88      
89        return maxResult;
90    }
91};
92
1function maxProduct(s: string): number {
2    const n = s.length;
3    let maxResult = 0;
4    let maxLeftPalindromeLength = 0;
5  
6    // Array to store the radius of palindrome centered at each position
7    const palindromeRadius: number[] = new Array(n).fill(0);
8    // Array to store the maximum palindrome length from position i to end
9    const maxRightPalindromeLength: number[] = new Array(n).fill(0);
10  
11    // Manacher's algorithm to find all palindrome centers and their radii
12    // This finds odd-length palindromes centered at each position
13    let left = 0;
14    let right = -1;
15    for (let i = 0; i < n; i++) {
16        // Initialize k based on whether we can use previously computed information
17        let k = (i > right) ? 1 : Math.min(palindromeRadius[left + right - i], right - i + 1);
18      
19        // Expand palindrome centered at i
20        while (i - k >= 0 && i + k < n && s[i - k] === s[i + k]) {
21            k++;
22        }
23      
24        // Store the radius (k-1 because we incremented one extra time)
25        palindromeRadius[i] = k - 1;
26      
27        // Update the rightmost palindrome boundary if needed
28        if (i + palindromeRadius[i] > right) {
29            left = i - palindromeRadius[i];
30            right = i + palindromeRadius[i];
31        }
32    }
33  
34    // Queue to maintain palindromes for calculating maximum right lengths
35    // Each element is [center, radius] pair
36    const rightQueue: [number, number][] = [];
37  
38    // Calculate maximum palindrome length from each position to the end
39    for (let i = n - 1; i >= 0; i--) {
40        // Remove palindromes that don't extend to current position
41        while (rightQueue.length > 0 && 
42               rightQueue[0][0] - rightQueue[0][1] > i - 1) {
43            rightQueue.shift();
44        }
45      
46        // Calculate maximum palindrome length from position i to end
47        // If queue is empty, only the character itself forms a palindrome (length 1)
48        // Otherwise, calculate based on the furthest reaching palindrome
49        maxRightPalindromeLength[i] = 1;
50        if (rightQueue.length > 0) {
51            maxRightPalindromeLength[i] += (rightQueue[0][0] - i) * 2;
52        }
53      
54        // Add current position and its palindrome radius to queue
55        rightQueue.push([i, palindromeRadius[i]]);
56    }
57  
58    // Queue to maintain palindromes for calculating maximum left lengths
59    // Each element is [center, radius] pair
60    const leftQueue: [number, number][] = [];
61  
62    // Calculate maximum palindrome length from start to each position
63    // and compute the maximum product
64    for (let i = 0; i < n - 1; i++) {
65        // Remove palindromes that don't extend to current position
66        while (leftQueue.length > 0 && 
67               leftQueue[0][0] + leftQueue[0][1] < i + 1) {
68            leftQueue.shift();
69        }
70      
71        // Calculate maximum palindrome length from start to position i
72        // If queue is empty, only the character itself forms a palindrome (length 1)
73        // Otherwise, calculate based on the furthest reaching palindrome
74        let currentLeftLength = 1;
75        if (leftQueue.length > 0) {
76            currentLeftLength += (i - leftQueue[0][0]) * 2;
77        }
78      
79        // Update the maximum left palindrome length seen so far
80        maxLeftPalindromeLength = Math.max(maxLeftPalindromeLength, currentLeftLength);
81      
82        // Calculate product of left and right palindrome lengths
83        // Left: from start to position i, Right: from position i+1 to end
84        maxResult = Math.max(maxResult, 
85                            maxLeftPalindromeLength * maxRightPalindromeLength[i + 1]);
86      
87        // Add current position and its palindrome radius to queue
88        leftQueue.push([i, palindromeRadius[i]]);
89    }
90  
91    return maxResult;
92}
93

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of several sequential phases, each taking linear time:

  1. Manacher's Algorithm Phase: The main loop runs n iterations. While there's a nested while loop, each character in the string is visited at most twice due to the optimization with center and right boundaries. This phase is O(n).

  2. Initial Prefix/Suffix Setup: A single loop of n iterations to populate initial values in prefix and suffix arrays based on palindrome half-lengths. This is O(n).

  3. Backward Propagation: A loop from index 1 to n-1 that updates prefix and suffix arrays using the negative indexing trick (~i). This processes each element once, taking O(n).

  4. Forward Accumulation: Another loop from index 1 to n-1 that computes the maximum values for prefix and suffix arrays by comparing with previous elements. This is O(n).

  5. Final Maximum Calculation: A loop through n-1 elements to compute the maximum product of non-overlapping palindromes. This is O(n).

Total time complexity: O(n) + O(n) + O(n) + O(n) + O(n) = O(n)

Space Complexity: O(n)

The algorithm uses the following auxiliary data structures:

  • hlen array of size n to store half-lengths of palindromes
  • prefix array of size n to store maximum palindrome lengths up to each position
  • suffix array of size n to store maximum palindrome lengths from each position
  • A few constant variables (center, right, i)

Total space complexity: O(n) + O(n) + O(n) + O(1) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Errors in Boundary Handling

One of the most common mistakes is incorrectly handling the boundaries when initializing prefix and suffix arrays. Developers often confuse whether an index represents the end position or the position after the end of a palindrome.

Incorrect Implementation:

# Wrong: Using i + hlen[i] - 1 as the end position
prefix[i + hlen[i] - 1] = max(prefix[i + hlen[i] - 1], 2 * hlen[i] + 1)

Correct Implementation:

# Right: The palindrome ends exactly at position i + hlen[i]
prefix[i + hlen[i]] = max(prefix[i + hlen[i]], 2 * hlen[i] + 1)

2. Incorrect Palindrome Length Propagation

When propagating palindrome lengths inward, it's easy to forget that removing one character from each end reduces the length by 2, not 1. Also, the propagation direction matters.

Incorrect Implementation:

# Wrong: Decrementing by 1 instead of 2
for i in range(1, n):
    prefix[i] = max(prefix[i], prefix[i - 1] - 1)
  
# Wrong: Propagating in the wrong direction
for i in range(1, n):
    prefix[i] = max(prefix[i], prefix[i - 1] - 2)  # Should go backward

Correct Implementation:

# Propagate backward for prefix (from right to left)
# Propagate forward for suffix (from left to right)
for i in range(1, n):
    prefix[n - 1 - i] = max(prefix[n - 1 - i], prefix[n - i] - 2)
    suffix[i] = max(suffix[i], suffix[i - 1] - 2)

3. Missing Cumulative Maximum Calculation

A critical mistake is forgetting to transform the prefix and suffix arrays into cumulative maximums. Without this step, you only have palindromes at specific positions, not the best palindromes in ranges.

Incorrect Implementation:

# Wrong: Using prefix/suffix arrays directly without cumulative max
max_product = 0
for i in range(1, n):
    # This only considers palindromes ending exactly at i-1 
    # and starting exactly at i
    product = prefix[i - 1] * suffix[i]
    max_product = max(max_product, product)

Correct Implementation:

# Transform to cumulative maximums first
for i in range(1, n):
    prefix[i] = max(prefix[i - 1], prefix[i])
    suffix[n - 1 - i] = max(suffix[n - 1 - i], suffix[n - i])

# Now prefix[i] contains the best palindrome in [0, i]
# and suffix[i] contains the best palindrome in [i, n-1]

4. Manacher's Algorithm Boundary Check Errors

When expanding palindromes, forgetting to check both boundaries can lead to index out of bounds errors.

Incorrect Implementation:

# Wrong: Missing boundary checks
while s[i - 1 - half_lengths[i]] == s[i + 1 + half_lengths[i]]:
    half_lengths[i] += 1

Correct Implementation:

# Include proper boundary checks
while (0 <= i - 1 - half_lengths[i] and 
       i + 1 + half_lengths[i] < n and 
       s[i - 1 - half_lengths[i]] == s[i + 1 + half_lengths[i]]):
    half_lengths[i] += 1

5. Confusion with the ~i Notation

The tilde operator ~i equals -(i+1), which when used as an array index in Python, accesses elements from the end. This can be confusing and lead to errors.

Alternative Clear Implementation:

# Instead of using ~i, be explicit:
for i in range(1, n):
    idx = n - 1 - i  # Clear backward iteration
    prefix[idx] = max(prefix[idx], prefix[idx + 1] - 2)
    suffix[i] = max(suffix[i], suffix[i - 1] - 2)

Prevention Tips:

  1. Draw out small examples and trace through the array indices manually
  2. Use descriptive variable names like end_pos and start_pos instead of just i + hlen[i]
  3. Add assertions or debug prints to verify array bounds during development
  4. Test with edge cases like single character strings and strings with no palindromes
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More