Problem
Given a string s
, find the longest palindromic subsequence in it. Return the actual subsequence (not just its length).
A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements. A palindromic subsequence is a subsequence that reads the same forward and backward.
Examples
Example 1
|
|
Example 2
|
|
Similar Problem
Longest Palindromic Subsequence 1 - Get Length Longest Palindromic Subsequence II
Solution
Palindromic subsequences are sequences within a string that read the same forwards and backwards, but do not require the characters to be contiguous. The challenge is to find the longest such subsequence, not just its length, but the actual sequence itself.
A simple way to check if a string is a palindrome is to use two pointers at each end and compare characters while moving inward. For subsequences, however, we need to consider all possible ways to delete characters and still maintain the order, which makes the problem more complex than checking substrings. For e.g. checkout Longest Common Subsequence Problem.
Method 1 - Naive
The naive approach is to generate all possible subsequences and check each for being a palindrome, and find the longest among them as the answer. This solution is exponential in term of time complexity. Also, as there are so many sub sequences to evaluate, approx 2^N where N is the number of characters in the given string.
Complexity
- ⏰ Time complexity:
O(2^N * N)
— There are 2^N possible subsequences, and checking each for being a palindrome takes up to O(N) time. - 🧺 Space complexity:
O(N)
— Space needed for storing a single subsequence during checking.
Method 2 - Recursion
This function returns length:
|
|
Here is the substructure of recursion:
|
|
In the above partial recursion tree, L(1, 4) is being solved twice. So, we have overlapping subproblems.
Method 2 – Recursive Approach
Intuition
The recursive approach explores all possible ways to form a palindromic subsequence by comparing characters at both ends of the string. If the characters match, they are included in the subsequence; otherwise, we recursively check by excluding one character from either end. This method builds the solution by breaking the problem into smaller subproblems.
Approach
- Define a recursive function that takes the string and two indices (start and end).
- If the start index exceeds the end index, return an empty string.
- If the start index equals the end index, return the single character (base case).
- If the characters at start and end match, include them and recurse for the inner substring.
- If they do not match, recursively find the longest palindromic subsequence by excluding either the start or end character, and return the longer result.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(2^N)
— Each character can be either included or excluded, leading to exponential subproblems. - 🧺 Space complexity:
O(N)
— Space for the recursion stack and storing the subsequence.
Method 3 – Top Down DP (Memoization)
Intuition
The memoized top-down approach improves upon the plain recursion by storing results of subproblems in a cache (memoization table). This avoids redundant calculations and speeds up the process, especially for overlapping subproblems.
Approach
- Use a recursive function similar to Method 2, but add a memoization table (2D array or map) to store results for each (i, j) pair.
- Before computing a result for (i, j), check if it is already in the memo table and return it if so.
- Otherwise, compute as in Method 2:
- If s[i] == s[j], include both and recurse for (i+1, j-1).
- Else, recurse for (i+1, j) and (i, j-1), and take the longer result.
- Store the result in the memo table before returning.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(N^2)
— There are O(N^2) unique subproblems, each solved once. - 🧺 Space complexity:
O(N^2)
— Space for the memoization table and recursion stack.
Method 4 – Bottom-Up DP (Tabulation)
Intuition
The bottom-up dynamic programming approach builds a solution by solving smaller subproblems first and using their results to solve larger ones. We fill a DP table where each cell represents the longest palindromic subsequence for a substring, and then reconstruct the actual subsequence from the table. This method is efficient and avoids recursion stack overhead.
Approach
- Create a DP table
dp
wheredp[i][j]
stores the length of the longest palindromic subsequence between indicesi
andj
. - Initialize all
dp[i][i]
to 1 (single characters are palindromes). - Fill the table for substrings of increasing length:
- If
s[i] == s[j]
, setdp[i][j] = dp[i+1][j-1] + 2
. - Otherwise, set
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
.
- If
- To reconstruct the subsequence, start from
dp[0][n-1]
and trace back through the table, adding matching characters to the result. - Use examples to illustrate how the table is filled and how the subsequence is built.
Example Dry Run
For s = "agbdba"
, the DP table is filled as follows:
- All diagonals are 1 (single characters).
- For length 2, compare pairs and fill accordingly.
- For longer substrings, use previously computed values.
- The final answer is built by tracing back from
dp[0][n-1]
.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(N^2)
— Filling the DP table and reconstructing the subsequence both take quadratic time. - 🧺 Space complexity:
O(N^2)
— The DP table uses quadratic space.