1960. Maximum Product of the Length of Two Palindromic Substrings
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]
ands[k...l]
where the indices satisfy0 <= 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 indexi
prefix[i]
: the maximum odd palindrome length ending at or before indexi
suffix[i]
: the maximum odd palindrome length starting at or after indexi
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.
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 ins[0...i]
suffix[i]
: maximum odd palindrome length ins[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 updateprefix[i + hlen[i]]
- It starts at position
i - hlen[i]
, so we updatesuffix[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 EvaluatorExample 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:
-
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 withcenter
andright
boundaries. This phase isO(n)
. -
Initial Prefix/Suffix Setup: A single loop of
n
iterations to populate initial values inprefix
andsuffix
arrays based on palindrome half-lengths. This isO(n)
. -
Backward Propagation: A loop from index 1 to
n-1
that updatesprefix
andsuffix
arrays using the negative indexing trick (~i
). This processes each element once, takingO(n)
. -
Forward Accumulation: Another loop from index 1 to
n-1
that computes the maximum values forprefix
andsuffix
arrays by comparing with previous elements. This isO(n)
. -
Final Maximum Calculation: A loop through
n-1
elements to compute the maximum product of non-overlapping palindromes. This isO(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 sizen
to store half-lengths of palindromesprefix
array of sizen
to store maximum palindrome lengths up to each positionsuffix
array of sizen
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:
- Draw out small examples and trace through the array indices manually
- Use descriptive variable names like
end_pos
andstart_pos
instead of justi + hlen[i]
- Add assertions or debug prints to verify array bounds during development
- Test with edge cases like single character strings and strings with no palindromes
Which of the following array represent a max heap?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!