LeetCode刷题--- 最长回文子序列

最长回文子序列

题目链接:最长回文子序列

题目

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成

解法

算法原理与解析

我们这题使用动态规划,我们做这类题目可以分为以下五个步骤

  1. 状态显示
  2. 状态转移方程
  3. 初始化(防止填表时不越界)
  4. 填表顺序
  5. 返回值
  • 状态显示
关于「单个字符串」问题中的「回⽂⼦序列」,或者「回⽂⼦串」,我们的状态表⽰研究的对象⼀般都是选取原字符串中的⼀段区域 [i, j] 内部的情况。这⾥我们继续选取字符串中的⼀段区域来研究:
dp[i][j] 表示 :s 字符串 [i, j] 区间内的所有的⼦序列中,最⻓的回⽂⼦序列的⻓度。
  • 状态转移方程
关于「回⽂⼦序列」和「回⽂⼦串」的分析⽅式,⼀般都是⽐较固定的,都是选择这段区域的「左右端点」的字符情况来分析。因为如果⼀个序列是回⽂串的话,「去掉⾸尾两个元素之后依旧是回文串」,「⾸尾加上两个相同的元素之后也依旧是回⽂串」。因为,根据「首尾元素」的不同,可以分为下⾯两种情况:
  1. 当首尾两个元素「相同」的时候,也就是 s[i] == s[j] :那么 [i, j] 区间上的最长回⽂⼦序列,应该是 [i + 1, j - 1] 区间内的那个最⻓回⽂⼦序列⾸尾填上 s[i] 和 s[j] ,此时 dp[i][j] = dp[i + 1][j - 1] + 2。
  2. 当⾸尾两个元素不「相同」的时候,也就是 s[i] != s[j] :此时这两个元素就不能同时添加在⼀个回⽂串的左右,那么我们就应该让 s[i] 单独加在⼀个序列的左边,或者让 s[j] 单独放在⼀个序列的右边,看看这两种情况下的最⼤值:
    1. 单独加⼊ s[i] 后的区间在 [i, j - 1] ,此时最⻓的回⽂序列的⻓度就是 dp[i]
      [j - 1]
    2. 单独加⼊ s[j] 后的区间在 [i + 1, j] ,此时最⻓的回⽂序列的⻓度就是 dp[i
      + 1][j]
  综上所述,状态转移⽅程为:
  • 当 s[i] == s[j] 时: dp[i][j] = dp[i + 1][j - 1] + 2
  • 当 s[i] != s[j] 时: dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
  • 初始化(防止填表时不越界)
我们的初始化⼀般就是为了处理在状态转移的过程中,遇到的⼀些边界情况,因为我们需要根据状态转移⽅程来分析哪些位置需要初始化。 根据状态转移⽅程 dp[i][j] = dp[i + 1][j - 1] + 2 ,我们状态表⽰的时候,选取的 是⼀段区间,因此需要要求左端点的值要⼩于等于右端点的值,因此会有两种边界情况:
  1. i == j 的时候, i + 1 就会⼤于 j - 1 ,此时区间内只有⼀个字符。这个⽐较好分析, dp[i][j] 表⽰⼀个字符的最⻓回⽂序列,⼀个字符能够⾃⼰组成回⽂串,因此此时 dp[i][j] = 1
  2. i + 1 == j 的时候, i + 1 也会⼤于 j - 1 ,此时区间内有两个字符。这样也好分析,当这两个字符相同的时候, dp[i][j] = 2 ;不相同的时候, d[i][j] = 0 。
  • 对于第⼀种边界情况,我们在填表的时候,就可以同步处理。
  • 对于第⼆种边界情况, dp[i + 1][j - 1] 的值为 0 ,不会影响最终的结果,因此可以不⽤ 考虑。
  • 填表顺序
根据「状态转移」,我们发现,在 dp 表所表⽰的矩阵中, dp[i + 1] 表⽰下⼀⾏的位置,dp[j - 1] 表⽰前⼀列的位置。因此我们的填表顺序应该是「从下往上填写每⼀⾏」,「每⼀⾏从左往右」。
  • 返回值
根据「状态表⽰」,我们需要返回 [0, n -1] 区域上的最⻓回⽂序列的⻓度,因此需要返回
dp[0][n - 1]

代码实现 

class Solution {
public:
    int longestPalindromeSubseq(string s) 
    {
        int n = s.size();
        vector<vector<int> > dp(n, vector<int>(n));		// 表示[i,j]位置最长的回文子序列

        // 填表,从下往上,从左往右
        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];
    }
};

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-元清-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值