Longest palindromic subsequence
Given a string s, return the length of its longest palindromic subsequence: characters taken in
order, not necessarily adjacent, that read the same forwards and backwards.
Examples
| Input | Output | Why |
|---|---|---|
s = "bbbab" | 4 | "bbbb". |
s = "cbbd" | 2 | "bb". |
s = "agbdba" | 5 | "abdba". |
Constraints
- 1 ≤ s.length ≤ 1000
- Lowercase English letters
Hints
Hint 1
Let L[i][j] be the answer for s[i..j]. A single character gives 1.
Hint 2
If s[i] = s[j], both ends can be used: L[i][j] = 2 + L[i + 1][j − 1]. Otherwise drop one end.
Hint 3
Fill by increasing interval length, or loop i downwards and j upwards.
Where people lose marks
- Confusing it with the longest palindromic substring, which must be contiguous.
- Filling rows in increasing i, which reads row i + 1 before it exists.
- Forgetting the case j = i + 1 with equal ends: the inside is empty, worth 0.
Dry run on "agbdba"
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 22
Base cases filled first.
-
Step 2 / 22
[a, a] = 1.
-
Step 3 / 22
[g, g] = 1.
-
Step 4 / 22
[b, b] = 1.
-
Step 5 / 22
[d, d] = 1.
-
Step 6 / 22
[b, b] = 1.
-
Step 7 / 22
[a, a] = 1.
-
Step 8 / 22
[a, g] = 1.
-
Step 9 / 22
[g, b] = 1.
-
Step 10 / 22
[b, d] = 1.
-
Step 11 / 22
[d, b] = 1.
-
Step 12 / 22
[b, a] = 1.
-
Step 13 / 22
[a, b] = 1.
-
Step 14 / 22
[g, d] = 1.
-
Step 15 / 22
[b, b] = 3.
-
Step 16 / 22
[d, a] = 1.
-
Step 17 / 22
[a, d] = 1.
-
Step 18 / 22
[g, b] = 3.
-
Step 19 / 22
[b, a] = 3.
-
Step 20 / 22
[a, b] = 3.
-
Step 21 / 22
[g, a] = 3.
-
Step 22 / 22
[a, a] = 5. The answer is L[0][5] = 5.
How to think about it
Interval DP. L[i][j] is the longest palindromic subsequence inside s[i..j]. When the ends match,
some optimal palindrome uses both of them, giving 2 + L[i + 1][j − 1]; when they differ, at least one end is
unused, giving max(L[i + 1][j], L[i][j − 1]). Every interval reads shorter ones, so iterate i from n − 1 down
to 0 and j from i up to n − 1. Only the row for i + 1 is needed while filling row i, so two rows suffice.
Complexity. Time O(n²) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def longest_pal_subseq(s):
n = len(s)
nxt = [0] * n # row i + 1 of the table
for i in range(n - 1, -1, -1):
cur = [0] * n
cur[i] = 1 # a single character
for j in range(i + 1, n):
if s[i] == s[j]:
cur[j] = 2 + (nxt[j - 1] if i + 1 <= j - 1 else 0)
else:
cur[j] = max(nxt[j], cur[j - 1])
nxt = cur
return nxt[n - 1]#include <algorithm>
#include <string>
#include <vector>
int solve(std::string s) {
const int n = s.size();
std::vector<int> nxt(n, 0), cur(n, 0); // rows i + 1 and i
for (int i = n - 1; i >= 0; --i) {
std::fill(cur.begin(), cur.end(), 0);
cur[i] = 1;
for (int j = i + 1; j < n; ++j)
cur[j] = s[i] == s[j] ? 2 + (i + 1 <= j - 1 ? nxt[j - 1] : 0) : std::max(nxt[j], cur[j - 1]);
std::swap(nxt, cur);
}
return nxt[n - 1];
}class Solution {
public int longestPalSubseq(String s) {
int n = s.length();
int[] nxt = new int[n], cur = new int[n]; // rows i + 1 and i
for (int i = n - 1; i >= 0; i--) {
java.util.Arrays.fill(cur, 0);
cur[i] = 1;
for (int j = i + 1; j < n; j++)
cur[j] = s.charAt(i) == s.charAt(j) ? 2 + (i + 1 <= j - 1 ? nxt[j - 1] : 0) : Math.max(nxt[j], cur[j - 1]);
int[] t = nxt; nxt = cur; cur = t;
}
return nxt[n - 1];
}
}function longestPalSubseq(s) {
const n = s.length;
let nxt = new Array(n).fill(0); // row i + 1
for (let i = n - 1; i >= 0; i--) {
const cur = new Array(n).fill(0);
cur[i] = 1;
for (let j = i + 1; j < n; j++)
cur[j] = s[i] === s[j] ? 2 + (i + 1 <= j - 1 ? nxt[j - 1] : 0) : Math.max(nxt[j], cur[j - 1]);
nxt = cur;
}
return nxt[n - 1];
}fn solve(s: &str) -> i64 {
let c: Vec<u8> = s.bytes().collect();
let n = c.len();
let mut nxt = vec![0i64; n]; // row i + 1
for i in (0..n).rev() {
let mut cur = vec![0i64; n];
cur[i] = 1;
for j in i + 1..n {
cur[j] = if c[i] == c[j] {
2 + if i + 1 <= j - 1 { nxt[j - 1] } else { 0 }
} else {
nxt[j].max(cur[j - 1])
};
}
nxt = cur;
}
nxt[n - 1]
}import Data.Array
solve :: String -> Int
solve s = table ! (0, n - 1)
where
n = length s
a = listArray (0, n - 1) s
-- a lazy table: each interval refers only to shorter ones
table = array ((0, 0), (n - 1, n - 1)) [((i, j), go i j) | i <- [0 .. n - 1], j <- [0 .. n - 1]] :: Array (Int, Int) Int
go i j
| i > j = 0
| i == j = 1
| a ! i == a ! j = 2 + (if i + 1 <= j - 1 then table ! (i + 1, j - 1) else 0)
| otherwise = max (table ! (i + 1, j)) (table ! (i, j - 1))
Variants to try
- The fewest insertions that make s a palindrome: n − L[0][n − 1].
- Return the palindrome itself.
- Count the distinct palindromic subsequences.
Next problems
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem →Write a solution and run it against the real test table.
Keep going
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.