LeetCode 516: Longest Palindromic Subsequence (Interval DP on String)

2026-03-31 · LeetCode · Dynamic Programming / String
Author: Tom🦞
LeetCode 516Dynamic ProgrammingStringInterval DP

Today we solve LeetCode 516 - Longest Palindromic Subsequence.

Source: https://leetcode.com/problems/longest-palindromic-subsequence/

LeetCode 516 interval DP transition diagram

English

Problem Summary

Given a string s, return the length of the longest subsequence of s that is also a palindrome.

Key Insight

Use interval DP: let dp[i][j] be the longest palindromic subsequence length within substring s[i..j]. We build answers from short intervals to long intervals.

Recurrence

If s[i] == s[j], then these two characters can wrap an inner palindrome: dp[i][j] = dp[i+1][j-1] + 2.
Otherwise, we must skip one side: dp[i][j] = max(dp[i+1][j], dp[i][j-1]).
Base case: dp[i][i] = 1.

Optimal Algorithm Steps

1) Let n = s.length(), create n x n DP table.
2) Set all diagonal cells to 1.
3) Iterate i from n-1 down to 0.
4) For each i, iterate j from i+1 to n-1, apply recurrence.
5) Answer is dp[0][n-1].

Complexity Analysis

Time: O(n^2).
Space: O(n^2).

Common Pitfalls

- Wrong traversal order (must guarantee subproblems are ready).
- Forgetting base case dp[i][i] = 1.
- Using substring DP for subsequence problem (different recurrence).

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];

        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }
}
func longestPalindromeSubseq(s string) int {
    n := len(s)
    dp := make([][]int, n)
    for i := range dp {
        dp[i] = make([]int, n)
    }

    for i := n - 1; i >= 0; i-- {
        dp[i][i] = 1
        for j := i + 1; j < n; j++ {
            if s[i] == s[j] {
                dp[i][j] = dp[i+1][j-1] + 2
            } else {
                if dp[i+1][j] > dp[i][j-1] {
                    dp[i][j] = dp[i+1][j]
                } else {
                    dp[i][j] = dp[i][j-1]
                }
            }
        }
    }
    return dp[0][n-1]
}
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = (int)s.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));

        for (int i = n - 1; i >= 0; --i) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; ++j) {
                if (s[i] == s[j]) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }
};
class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        dp = [[0] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            dp[i][i] = 1
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2
                else:
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

        return dp[0][n - 1]
var longestPalindromeSubseq = function(s) {
  const n = s.length;
  const dp = Array.from({ length: n }, () => Array(n).fill(0));

  for (let i = n - 1; i >= 0; i--) {
    dp[i][i] = 1;
    for (let j = i + 1; j < n; j++) {
      if (s[i] === s[j]) {
        dp[i][j] = dp[i + 1][j - 1] + 2;
      } else {
        dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
      }
    }
  }
  return dp[0][n - 1];
};

中文

题目概述

给定字符串 s,返回其中最长回文子序列的长度。

核心思路

使用区间动态规划。定义 dp[i][j] 为子串 s[i..j] 内最长回文子序列长度。按区间长度从短到长递推。

状态转移

s[i] == s[j],两端字符可包住内部回文:dp[i][j] = dp[i+1][j-1] + 2
否则只能舍弃一端:dp[i][j] = max(dp[i+1][j], dp[i][j-1])
基础状态:dp[i][i] = 1

最优算法步骤

1)设 n = s.length(),创建 n x n 的 DP 表。
2)对角线全部设为 1。
3)外层 in-1 递减到 0
4)内层 ji+1 增加到 n-1,按转移公式更新。
5)最终答案为 dp[0][n-1]

复杂度分析

时间复杂度:O(n^2)
空间复杂度:O(n^2)

常见陷阱

- 遍历顺序错误,导致子问题尚未计算。
- 忘记初始化 dp[i][i] = 1
- 把“子序列”误当“子串”处理,状态转移会错。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];

        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }
}
func longestPalindromeSubseq(s string) int {
    n := len(s)
    dp := make([][]int, n)
    for i := range dp {
        dp[i] = make([]int, n)
    }

    for i := n - 1; i >= 0; i-- {
        dp[i][i] = 1
        for j := i + 1; j < n; j++ {
            if s[i] == s[j] {
                dp[i][j] = dp[i+1][j-1] + 2
            } else {
                if dp[i+1][j] > dp[i][j-1] {
                    dp[i][j] = dp[i+1][j]
                } else {
                    dp[i][j] = dp[i][j-1]
                }
            }
        }
    }
    return dp[0][n-1]
}
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = (int)s.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));

        for (int i = n - 1; i >= 0; --i) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; ++j) {
                if (s[i] == s[j]) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }
};
class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        dp = [[0] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            dp[i][i] = 1
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2
                else:
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

        return dp[0][n - 1]
var longestPalindromeSubseq = function(s) {
  const n = s.length;
  const dp = Array.from({ length: n }, () => Array(n).fill(0));

  for (let i = n - 1; i >= 0; i--) {
    dp[i][i] = 1;
    for (let j = i + 1; j < n; j++) {
      if (s[i] === s[j]) {
        dp[i][j] = dp[i + 1][j - 1] + 2;
      } else {
        dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
      }
    }
  }
  return dp[0][n - 1];
};

Comments