【动归】B020_最长回文子序列(递归 | 记忆化搜索 | 二维 dp | 一维 dp)

一、题目描述

Given a string s, find the longest palindromic subsequence's length in s. 
You may assume that the maximum length of s is 1000.

Input:
"bbbab"
Output:
4

二、题解

方法一:递归(超时)

  • 结束条件
    • 两个指针背道而驰时,证明没有找到一个回文串,返回 0
    • 当两个指针重合时,代表找到的一个回文串是单个字母本身,返回 1
  • 本层递归的责任
    • 定义双指针从字符串 S 的两边向中间逼近,
      • 当两个字符相等时 S[l] == S[r],我们继续向中间逼近
      • 否则,我们只移动一边,这意味着会有两种选择,直接取二者最大值即可。
        • dfs(l + 1, r)
        • dfs(l, r - 1)

s = "AxxxxA" ,s[l] == s[r]

AxxxxA
|    |
l    r

忽略中间的一串 xxxx,我们已经得到一个长度为 2 的回文子序列,
我们只需让 l++,r-- 去求解规模小 2 的子问题

AxxxxA
 |  |
 l  r

s = "AxxxxB" ,s[l] != s[r]

AxxxxB
|    |
l    r

这时,我们一次只让字符串的某一边缩减规模,试探哪一种缩减方式可以得到的解最大

缩减方式一:l++
AxxxxB
 |   |
 l   r
 
缩减方式二:r--
AxxxxB
|   |
l   r
public int longestPalindromeSubseq(String s) {
  char[] S = s.toCharArray();
  return dfs(S, 0, S.length - 1);
}

private int dfs(char[] S, int l, int r) {
  if (l > r)  return 0;
  if (l == r) return 1;
  
  if (S[l] == S[r]) return dfs(S, l + 1, r - 1) + 2;
  else              return Math.max(dfs(S, l + 1, r), dfs(S, l, r-1));
}

复杂度分析

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

方法二:记忆化搜索

使用 memo 数组存储指针 l 与 r 在每一个位置时字符串 S 的状态。

int[][] memo = null;
private int dfs(char[] S, int l, int r) {
  if (l > r)  return 0;
  if (l == r) return 1; //只有一个字母时,当然返回1
  if (memo[l][r] != 0)
    return memo[l][r];

  if (S[l] == S[r]) memo[l][r] = dfs(S, l + 1, r- 1) + 2;
  else              memo[l][r] = Math.max(dfs(S, l + 1, r), dfs(S, l, r-1));

  return memo[l][r];
}

复杂度分析

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

方法三;二维 dp

  • 定义状态
    • d p [ i ] [ j ] dp[i][j] 表示字符串 S [ i , j ] S[i, j] 内部回文子序列最长长度。
  • 思考状态转移方程
    • S [ i ] = S [ j ] S[i] = S[j] 时, d p [ i ] [ j ] = d p [ i 1 ] [ j 1 ] + 2 dp[i][j] = dp[i-1][j-1] + 2 ,表示在先前找到的 LPS 的长度基础上 +2.
    • S [ i ] ! = S [ j ] S[i] != S[j] 时,两边各自移动一步,取两种状态的最大值。
      • d p [ i ] [ j ] = m a x ( d p [ i 1 ] [ j ] ,   d p [ i ] [ j + 1 ] ) dp[i][j] = max(dp[i-1][j],\ dp[i][j+1])
  • 思考初始化:
    • d p [ i ] [ i ] = 1 dp[i][i] = 1 表示单个字母的回文子序列长度为 1.
  • 思考输出 d p [ 0 ] [ N 1 ] dp[0][N-1] ,由上面的状态可将定义为 字符串 S [ 0... N 1 ] S[0...N-1] 内部 LPS 的最大长度。
public int longestPalindromeSubseq2(String s) {
  int N = s.length();
  char[] S = s.toCharArray();
  int[][] dp = new int[N][N];
  //最短回文子序列为自身
  for (int i = 0; i < N; i++) 
  	dp[i][i] = 1;

  for (int i = N-1; i >= 0; i--)
  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] = Math.max(dp[i][j-1], dp[i+1][j]);
  }
  return dp[0][N-1];
}

复杂度分析

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

方法四:一维 dp


发布了461 篇原创文章 · 获赞 102 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/104721062