【LeetCode】5. Longest Palindromic Substring

算法小白,最近刷LeetCode。希望能够结合自己的思考和别人优秀的代码,对题目和解法进行更加清晰详细的解释,供大家参考^_^

5. Longest Palindromic Substring

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

Example:

Input: "babad"

Output: "bab"

Note: “aba” is also a valid answer.

Example:

Input: "cbbd"

Output: "bb"

题目让找出最长的回文子串。这道题一开始只能想到枚举所有区间,再进行回文判断的方式,这样的方法肯定是会超时的,最后看了题解,说是要用动态规划。

本题的动规思路是,如果区间[i, j] (i<=j) 构成一个回文串,那么区间[i-1, j+1]构成回文串的充要条件是 str[i-1]==str[j+1], 因此基本的递推公式为:
dp[i, j] = dp[i+1, j-1] && str[i+1] == str[j-1]
dp[i, j]是bool型,表示区间 [i, j] 构成的子串是否是个回文串,显然这里 i<=j
:这里使用的是左闭右闭区间,C++规范一般要求使用左闭右开区间

有了递推公式,接下来就得考虑初值了,很显然,dp[i, i] (i=0…n)是等于true的,但只有这一类的初值明显不够,无法完成递推计算,还需要d[i, i+1] 这类的初值,显然,d[i, i+1] = (str[i] == str[i+1])

OK, 递推公式和初值都有了,接下来就可以码代码了。

class Solution {
public:
    bool dp[1000+5][1000+5];
    string longestPalindrome(string s) {
        int len = s.length();
        if (len == 1) return s;
        if (len == 2) {
            if (s[0] == s[1]) return s;
            else return s.substr(0, 1);
        } // 特殊情况直接返回

        int beg = 0, end = 0;
        int maxlen = 1;
        memset(dp, false, sizeof(dp));
        // 生成初值,即dp[i,i]和dp[i,i+1]
        for (int i = 0; i < len; ++i){
            dp[i][i] = true;
            if (i+1 < len) {
                dp[i][i+1] = (s[i] == s[i+1]);
                if (dp[i][i+1]) { beg = i; end = i + 1; maxlen = 2; }
            }

        }
        // 利用递推公式,同时更新最大长度和对应的区间下标
        for (int i = 2; i < len; ++i){
            for (int j = 0; j+i < len; ++j){
                dp[j][j+i] = ( dp[j+1][j+i-1] && (s[j] == s[j+i]) );
                if (dp[j][j+i] && i+1 > maxlen){
                    beg = j; end = j + i; maxlen = i+1;
                }
            }
        }

        return s.substr(beg, maxlen);
    }
};

将dp看做一个方阵,从递推公式中,我们可以观察到,dp[i, j] 的值只与其左下角的值dp[i+1, j-1] 有关,因此,从空间上来说,并没有必要记录所有的 i, j 组合,在更新时完全可以覆盖后续不再用到的空间,因此,只需要一个两行的数组就可以满足要求。

另一点要注意的时,由于更新时要使用左下角即靠前的值,因此要进行倒序更新,代码如下:

class Solution {
public:
    bool dp[2][1000+5]; // 使用两行就够了
    string longestPalindrome(string s) {
        int len = s.length();
        if (len == 1) return s;
        if (len == 2) {
            if (s[0] == s[1]) return s;
            else return s.substr(0, 1);
        }

        int beg = 0, end = 0;
        int maxlen = 1;
        memset(dp, true, sizeof(dp)); // 主要是给第一行赋初值,即dp[i][i],全部为true
        // 第二行进行手动更新,对应之前代码中的dp[i, i+1]
        for (int i = len - 1; i - 1 >= 0; --i){
            dp[1][i] = (s[i] == s[i-1]);
            if (dp[1][i]) {
                beg = i-1; end = i; maxlen = 2; 
            }
        }

        for (int i = 2; i < len; ++i){
            for (int j = len - 1; j - i >= 0; --j) { // 进行倒序更新,覆盖不再用到的值
                dp[i%2][j] = (dp[(i%2)][j-1] && (s[j] == s[j-i])); 
                if (dp[i%2][j] && i + 1 > maxlen){
                    beg = j - i; end = j; maxlen = i+1; }
            }
        }
        return s.substr(beg, maxlen);
    }
};

上述方法的时间复杂度都是O(n^2),提交后也只是打败了20%的小伙伴。

还有一种中心扩展的方法,不过他的时间复杂度也是O(n^2),这里先不介绍了,题解里面说,还有一种O(n)的牛X方法,叫做Manacher算法,等以后有机会再慢慢研究

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值