最长回文子序列LPS

11 篇文章 0 订阅

最长回文子序列 - LeetCode (中国)

原文地址:https://mintlucas.github.io/2019/04/21/最长回文子序列LPS/

题目描述:

给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。
示例 1:

    输入:

        "bbbab"

    输出:

        4

    一个可能的最长回文子序列为 "bbbb"。
示例 2:

    输入:

        "cbbd"

    输出:

        2

    一个可能的最长回文子序列为 "bb"。

思路:

经典的动态规划问题

记:

dp[i][j] := 子串 s[i..j] 的最长回文子序列的大小

初始化:

dp[i][j] = 1    i=j //s[i..i]为单个字母

         = 0    other

递推公式:

dp[i][j] = dp[i+1][j-1] + 2             s[i]==s[j]

         = max(dp[i+1][j], dp[i][j-1])  other

1.如果str的最后一个元素和第一个元素是相同的,则有:lps(0,n-1)=lps(1,n-2)+2;例如字符串序列“AABACACBA”,第一个元素和最后一个元素相同,其中lps(1,n-2)表示红色部分的最长回文子序列的长度;

2.如果str的最后一个元素和第一个元素是不相同的,则有:lps(0,n-1)=max(lps(1,n-1),lps(0,n-2));例如字符串序列“ABACACB”,其中lps(1,n-1)表示去掉第一元素的子序列,lps(0,n-2)表示去掉最后一个元素的子序列。

如果 `i+1=j` 且 `s[i]=s[j]`时,`dp[i][j] = dp[i+1][j-1] + 2` 依然成立,

因为 `i != j` 时,有 `dp[i][j] = 0`

注意,如果按照上面的 dp 定义,返回值应该是 dp[1][n] 而不是 dp[n][n]

明白状态转移之间的关系是理解所有动态规划问题的关键,也是难点

s[i]==s[j] 时,s[i..j] 的上一个状态应该是 s[i+1..j-1],两头收缩,而不是 s[i+1..j+1] 或者其他,明白这一点是理解本题的关键

s[i]!=s[j] 时,s[i..j] 的上一个状态应该是 s[i+1..j] 或 s[i..j-1]

暂不考虑 DP 的优化

状态更新的顺序也是一个注意点:

正确示例:

//abcdef a-a-1 ... c-c-1结束位置控制全局

for (int j = 1; j < n; j++)             // 子串结束位置

    for (int i = j-1; i >=0; i--) {     // 子串开始位置

//abcdef f-f+1 ... d-d+1开始位置控制全局

for (int i = n-1; i >= 0; i--)          // 子串开始位置(>=0)

    for (int j = i + 1; j < n; j++) {   // 子串结束位置(<n)

for (int j = 1; j < n; j++)             //子串长度(s[i..i+j])

    for (int i = 0; j + i < n; i++){    //子串开始位置(开始到结束闭区间<n)

错误示例:

for (int j = 1; j < n; j++)         // 子串结束位置

    for (int i = 0; i < j; i++) {   // 子串起始位置

// 但是该顺序在 "./最长回文子串.hpp" 中是正确的

注意:

因为 DP 的定义为了便于理解,下标常从 1 开始,这与一般容器的下标不同,所以

应该使用编码时应该使用 `s[i-1]==s[j-1]` 而不是 `s[i]==s[j]`

**当然也可以在编码时将 DP 的下标也调整至从 0 开始**

最后,这题只返回了最长回文子序列的长度,一般面试题中也只是要求返回长度即可。

但是如果你也想知道最长回文子序列具体是啥,这可以额外添加一个变量记录最长回文子序列是哪些字符,例如维护一个键为 lps[j][i + j],值为 String 的 map。

class Solution {

public:

    int longestPalindromeSubseq(string s) {

        int n = s.length();



        /* 更直观的循环方式

        */

        vector<vector<int>> dp(n, vector<int>(n, 0));

        for (int i = 0; i < n; i++)

            dp[i][i] = 1;

            

        for (int j = 1; j < n; j++)             // 子串结束位置

            for (int i = j-1; i >=0; i--) {     // 子串开始位置

                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];



        /* 下标从 0 开始

        vector<vector<int>> dp(n, vector<int>(n, 0));

        for (int i = 0; i < n; i++)

            dp[i][i] = 1;

            

        for (int i = n-1; i >= 0; i--)          // 子串开始位置(>=0)

            for (int j = i + 1; j < n; j++) {   // 子串结束位置(<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];

        */

        

        /*

        int n = s.size();

        vector<vector<int>> dp(n+1, vector<int>(n+1, 0));

        for(int i = 0; i < n; ++i)

            dp[i][i] = 1;

        

        for(int j = 1; j < n; ++j)             //子串长度(s[i..i+j]

            //注意此处为i+j<len

            for(int i = 0; i + j < n; ++i){    //子串开始位置(开始到结束闭区间<n)

                //s[i...i+j]

                if(s[i] == s[i+j])

                    dp[i][i+j] = dp[i+1][i+j-1] + 2;

                else

                    dp[i][i+j] = max(dp[i+1][i+j], dp[i][i+j-1]);

            }

        

        return dp[0][n-1];

        */

         

        /*下标从 1 开始

        vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));

        //初始化时记得i<=n

        for (int i = 1; i <= n; i++)

            dp[i][i] = 1;

            

        for (int i = n; i >=1; i--)

            for (int j = i+1; j <= n; j++) {

                if (s[i-1] == s[j-1])

                    dp[i][j] = dp[i + 1][j - 1] + 2;

                else

                    dp[i][j] = max({ dp[i + 1][j], dp[i][j - 1] });

            }

        

        return dp[1][n];

        */

    }

};



int main(){

    string t = "cbbd";

    int res = Solution().longestPalindromeSubseq(t);

    

}


最长回文子串 - LeetCode (中国)

问题描述:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。

示例 1:

    输入: "babad"

    输出: "bab"

    注意: "aba"也是一个有效答案。

示例 2:

    输入: "cbbd"

    输出: "bb"

思路:

动态规划

如果只是求最长回文子串的长度,其递推公式与 "最长回文子序列" 完全相同

这里需要给出具体的子串,需要重新定义 dp

定义:

    dp[i][j] := 子串 s[i..j] 是否是回文子串

初始化

    dp[i][j] = true     i=j

             = false    other

递推公式:

    dp[i][j] = s[i]==s[j]                   j-i=1

             = s[i]==s[j]&&dp[i+1][j-1]      j-i>1

本题除了动态规划的另一个难点是如何保存其中一个子串

class Solution {

public:

    string longestPalindrome(string s) {

        int n = s.length();



        vector<vector<int>> dp(n, vector<int>(n, 0));

        

        //用下标来记录,防止出现单一回文情况

        int max_len = 1;    //保存最长回文子串长度

        int start = 0;      //保存最长回文子串起点

        

        //或string max_str;但j=i开始,计算单一回文的情况

        //max_str = s.substr(i, j-i+1); return max_str;

        

        for (int i = 0; i < n; i++)

            dp[i][i] = 1;



        //for (int j = 1; j < n; j++)         // 子串结束位置

        //    for (int i = 0; i < j; i++) {   // 子串起始位置

        // (上)循环方式是正确的,但在 "./最长回文子序列" 中不对

        // (下)在两个问题中都正确

        for (int j = 1; j < n; j++)             // 子串结束位置

            for (int i = j - 1; i >= 0; i--) {  // 子串开始位置

                if (j - i < 2)

                    dp[i][j] = (s[i] == s[j]);

                else

                    dp[i][j] = (s[i] == s[j] && dp[i + 1][j - 1]);



                // 保存子串

                if (dp[i][j] && (j - i + 1) >= max_len) {

                    max_len = j - i + 1;

                    start = i;

                }

            }

        return s.substr(start, max_len);

    }

};


刚开始的想法是直接DFS,但会超时,也记录一下吧

bool isPalindrome1(string s){

    return s == string(s.rbegin(), s.rend());

}

void dfsPalindrome(string s, string &res){

    if(s == "")

        return ;

    for (int i = 1; i <= s.size(); ++i) {

        string temp = s.substr(0, i);

        if(isPalindrome1(temp)){

            if(temp.size() > res.size())

                res = temp;

            dfsPalindrome(s.substr(i), res);

        }

    }

}

string longestPalindrome(string s) {

    string res;

    dfsPalindrome(s, res);

    return res;

}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值