LeetCode 1682 - Longest Palindromic Subsequence II
The problem is asking us to find the length of the longest good palindromic subsequence in a given string s. A good pali
Difficulty: 🟡 Medium
Topics: String, Dynamic Programming
Solution
Problem Understanding
The problem is asking us to find the length of the longest good palindromic subsequence in a given string s. A good palindromic subsequence is a string that meets several conditions: it must be a subsequence of s, it must read the same forwards and backwards, it must have an even length, and it cannot contain two consecutive equal characters except for the middle two characters.
The input s is a string of lowercase English letters with length up to 250, which is small enough to consider dynamic programming solutions with O(n^3) time complexity in practice, though ideally we want O(n^2) if possible. The output is an integer representing the length of the longest good palindromic subsequence.
Important edge cases include strings where all characters are the same, strings with only one character, strings where no good palindromic subsequence exists, and strings where the optimal subsequence is nested deeply inside the string rather than at the ends. The problem guarantees at least one character, so an empty string input does not need to be handled.
Approaches
A brute-force approach would be to generate all possible subsequences of s, check each one for being a palindrome, even length, and non-repeating adjacent characters. This approach is correct because it exhaustively considers all possibilities, but it is impractical: generating subsequences has exponential time complexity, specifically O(2^n) for a string of length n, which would be infeasible for n up to 250.
The optimal approach is to use dynamic programming. We define dp[i][j] as the length of the longest good palindromic subsequence in the substring s[i:j+1]. We iterate over substring lengths and fill dp[i][j] based on whether s[i] equals s[j] and whether choosing them maintains the "no consecutive duplicates" property. The insight is that we can build larger palindromes from smaller ones by checking these conditions and tracking the maximum valid length.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n * n) | O(n) | Generate all subsequences and check conditions |
| Optimal | O(n^3) | O(n^2) | DP over substring indices with checking for consecutive duplicates |
Algorithm Walkthrough
- Initialize a 2D DP array
dpof sizen x nfilled with zeros, wherenis the length of the string. - Iterate over all substrings of
sby lengthlen_subfrom 2 ton. - For each substring starting at index
iand ending at indexj = i + len_sub - 1, check ifs[i] == s[j]. - If the characters are equal, look inside the substring
s[i+1:j]for the longest valid subsequence and ensure that addings[i]ands[j]does not violate the "no consecutive duplicates" rule. - Otherwise, take the maximum between
dp[i+1][j]anddp[i][j-1]. - Keep track of the maximum even length found during this process.
- Return the maximum length stored in
dp[0][n-1]after iterating over all substrings.
Why it works: This works because each dp[i][j] correctly represents the longest valid subsequence in s[i:j+1] by considering all possible choices for the endpoints. By building from smaller substrings to larger ones, we guarantee that all dependencies are resolved before use.
Python Solution
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 0 # single character cannot be even-length good palindrome
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
if length == 2:
dp[i][j] = 2
elif s[i] != s[i + 1] and s[j] != s[j - 1]:
dp[i][j] = dp[i + 1][j - 1] + 2
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
return max(max(row) for row in dp)
This implementation initializes the DP table, fills it iteratively while checking the rules for a good palindromic subsequence, and returns the maximum length found. Single-character substrings are initialized as zero because they cannot form even-length palindromes.
Go Solution
func longestPalindromeSubseq(s string) int {
n := len(s)
dp := make([][]int, n)
for i := 0; i < n; i++ {
dp[i] = make([]int, n)
}
for length := 2; length <= n; length++ {
for i := 0; i <= n - length; i++ {
j := i + length - 1
if s[i] == s[j] {
if length == 2 {
dp[i][j] = 2
} else if s[i] != s[i+1] && s[j] != s[j-1] {
dp[i][j] = dp[i+1][j-1] + 2
} else {
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
}
} else {
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
}
}
}
maxLen := 0
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if dp[i][j] > maxLen {
maxLen = dp[i][j]
}
}
}
return maxLen
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
In Go, the implementation uses slices of slices to create the DP table. The logic mirrors Python, with careful index checks to prevent out-of-range errors. A helper function max is used to simplify comparisons.
Worked Examples
Example 1: s = "bbabab"
| i | j | s[i] == s[j] | dp[i][j] |
|---|---|---|---|
| 0 | 1 | yes ('b') | 2 |
| 1 | 2 | no | max(dp[2][2], dp[1][1]) = 0 |
| 0 | 5 | no | max(dp[1][5], dp[0][4]) = 4 |
Longest good palindromic subsequence is "baab", length 4.
Example 2: s = "dcbccacdb"
Longest good palindromic subsequence is "dccd", length 4. The DP table is built similarly, ensuring no consecutive duplicates at non-central positions.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n^3) | Three nested loops: substring length, start index, and inner DP check for consecutive duplicates |
| Space | O(n^2) | DP table of size n x n |
This complexity is acceptable for n <= 250, giving a maximum of around 15 million operations in the worst case.
Test Cases
# provided examples
assert Solution().longestPalindromeSubseq("bbabab") == 4 # "baab"
assert Solution().longestPalindromeSubseq("dcbccacdb") == 4 # "dccd"
# edge cases
assert Solution().longestPalindromeSubseq("a") == 0 # single character
assert Solution().longestPalindromeSubseq("aa") == 2 # two same characters
assert Solution().longestPalindromeSubseq("ab") == 0 # two different characters
assert Solution().longestPalindromeSubseq("abcabcabb") == 4 # "abba" is longest
assert Solution().longestPalindromeSubseq("ababab") == 4 # "abba" or "baba"
| Test | Why |
|---|---|
| "bbabab" | Validates basic DP computation and consecutive character check |
| "dcbccacdb" | Checks non-trivial palindromic subsequence with internal duplicates |
| "a" | Ensures single-character case returns 0 |
| "aa" | Tests minimal even-length palindrome |
| "ab" | Tests minimal invalid palindrome |
| "abcabcabb" | Longer string with nested palindromes |
| "ababab" | Repeating patterns, multiple valid options |
Edge Cases
The first edge case is a single-character string, like "a". It is impossible to form an even-length palindrome, so the result should be 0. The implementation correctly