To find the longest palindromic substring in a given string, we look for sequences that read the same forward and backward. This problem is a perfect candidate for dynamic programming because we can build answers to bigger problems (long substrings) using answers to smaller problems (short substrings).
What’s the core idea?
A substring is a palindrome if the characters at the ends are equal and the part in between is also a palindrome. So we use a table (2D array) to record whether each substring s[i..j]
is a palindrome.
How does dynamic programming help?
We create a 2D boolean table dp[i][j]
where each cell tells us if the substring from index i
to j
is a palindrome. Once we know the result of smaller substrings, we can reuse that information to determine if a larger substring is also a palindrome.
How do we fill the table?
- Substrings of length 1: Every single character is a palindrome. So we set
dp[i][i] = true
.
- Substrings of length 2: These need special handling. We can't apply the general DP rule (checking the inner substring) because there's nothing between two characters. So instead, we just check if
s[i] == s[i+1]
, and if so, mark dp[i][i+1] = true
.
Why do this separately? If we applied the usual rule dp[i+1][j-1]
here, we’d be accessing invalid indices when i+1 > j-1
. So we handle 2-letter substrings directly and use them as a base to build longer palindromes like "abba".
- Substrings of length ≥ 3: For any substring
s[i..j]
, we check:
- if
s[i] == s[j]
- and if
dp[i+1][j-1]
is true (i.e., the inner part is already a palindrome)
If both conditions are met, we mark dp[i][j] = true
.
How do we track the result?
While filling the dp
table, we keep track of the longest palindrome found so far. If we find a new valid palindrome that’s longer than the previous one, we update the starting index and max length. At the end, we extract and return the longest substring using these values.
What about special cases?
- Empty input: If the input string is empty, we return an empty string.
- Multiple answers: For strings like "babad", both "bab" and "aba" are valid. We can return either.
Why this works efficiently
Without DP, we might check the same substring repeatedly. With DP, once we compute whether s[i..j]
is a palindrome, we store it and never recompute it again. This gives us an efficient O(n²) solution, which works well for strings up to 1000 characters.