LeetCode 5. 最长回文子串

5. 最长回文子串

这道题目是要找到一个字符串中的最长回文子串。主要有两种方法:1. 中心扩展;2. 动态规划。两种方法都没想到最开始,后来看了解析,自己理解了会了。

一、中心扩散法

  中心扩散法,比较好理解,就是根据回文字符串的定义:是中心对称的。因此,我们先定义一个中心的字符。这里要注意,一个回文字符串的长度可能是奇数,也可能是偶数,因此要加以区分。如果是奇数,那么最中间必定是一个字符,如果是偶数那么中间是空。
  为了找到最长的回文串,对于每个字符,我们考虑两个情况:如果这个字符是中心的(奇数),如果不是中心的。然后,因为是要找最长的,因此我们比较奇数和偶数中比较长的,然后和之前的结果进行比较。
  得到的针对每个字符找回文串的函数如下:

// 此函数用来找回文子串:从中间向两边寻找
    string findback(string s, int left, int right) {
        int i = left, j = right;
        while (i >= 0 && right < s.size()) {
            if (s[i] == s[j]) {
                i--;
                j++;
            }
            else {
                break;
            }
        }
        // 特别注意的是,返回的范围。因为此时已经过了刚好是回文的那一截。
        return s.substr(i + 1, j - i - 1);
    }

  主函数中,从头开始遍历,针对每个字符的情况进行判断。因此,主函数如下:

string longestPalindrome(string s) {
        int len = s.size();
        // 特殊情况
        if (len < 2) {
            return s;
        }

        string odd, even, maxstr, res = s.substr(0, 1);
        int maxlen = 1;
        // 循环处理
        for (int i = 0; i < len - 1; i++) {     // 这里是len - 1,相当于一个剪枝操作
            // 分别计算奇数长度的回文字符串和偶数长度的回文字符串
            odd = findback(s, i, i);
            even = findback(s, i, i + 1);
            // 选取两者之间最长的
            maxstr = odd.size() > even.size() ? odd : even;
            // 更新
            if (maxstr.size() > maxlen) {
                maxlen = maxstr.size();
                res = maxstr;
            } 
        }
        return res;
    }

二、动态规划

  动态规划法,应该来说是必须要掌握的。现在这个阶段,我对动态规划的理解还不够深刻,自己做的题尚少,因此看了解析,根据我的理解,把解析写下来。

1. 状态的定义
一开始我想的是,用一个一维数组来记录每个位置的回文的长度,后面发现这种思路根本行不通:第一,在某个字符的位置时,不能通过当前字符串来判断是否为一个回文串,你必须考虑前面的;第二,状态无法转移,如果前面是回文,那么你怎么判断加起来是不是回文呢?这样又必须遍历一遍了。
看了解析以后明白了。需要用一个二维数组来表示,并且二维数组是bool类型的而不是int。具体的用dp[i][j]来表示字符串中从位置i到位置j的子串是否为回文串。

2. 状态的转移
状态的转移也是一个比较关键的地方:从哪个状态转移到哪个状态,这就跟回文字符串的定义有关系了。如果当前子串(从位置i到j)是回文串,那么i+1到j-1(比当前回文串刚好小的那个)肯定也是子串,并且s[i] = s[j],因此可以写出状态转移方程如下:

dp[i][j] = (s[i] == s[j]) and (dp[i+1][j-1] == true)

同时,我们还需要考虑一些约束条件和边界条件。
(1) i肯定要比j小;
(2)如果子串的长度小于2了,那么肯定是回文(因为为一个字符或者空字符,肯定是回文的);因此有:j - 1 - (i + 1) + 1 < 2;

3. 状态的初始化
当为单个字符时(即i == j)时,肯定是回文的。因此我们初始化dp[i][j]的对角线全为true;
但,事实上,初始化的部分都可以省去。因为只有一个字符的时候一定是回文,dp[i][i] 根本不会被其它状态值所参考。

4. 输出
我们在这里得到了i和j的值的时候,没必要每次都截取字符串,只需要记录两个关键值:起始位置,和长度,并且与之前的长度进行比较,选取较大的一个即可。

考虑到以上问题后,进行代码的编写,最终如下:

class Solution {
    public:
    //动态规划法
    string longestPalindrome(string s) {
        const int len = s.size();
        // 特殊情况
        if (len < 2) {
            return s;
        }

        string res;
        int begin = 0, maxlen = 1;
        bool dp[len][len];
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        for (int j = 1; j < len; j++) {     // 注意起始范围
            for (int i = 0; i < j; i++) {   // 注意起始范围,i从开始一直到j,取到所有的
                if (s[i] == s[j]) {        // 如果首末的字符相同,那么进行判断
                    if (j - i < 3) {    //如果小于边界值:为单个字符或者空的情况,肯定是真
                        dp[i][j] = true;
                    }
                    else {
                        dp[i][j] = dp[i + 1][j - 1];   // 否则根据表中左下角的值来判断
                    }
                }
                else {      //  如果不等,直接判断为false
                    dp[i][j] = false;
                }

                // 然后进行判断,如果当前的是回文串,那么和之前的进行比较
                if (dp[i][j] && j - i + 1 > maxlen) {
                    begin = i;
                    maxlen = j - i + 1;
                }
            }
        }
        res = s.substr(begin, maxlen);
        return res;
    }
};

不过让我奇怪的是,这个题虽然需要用到动态规划,但是竟然没有使用递归函数,这点让我刷新了对递归函数的理解。

  总的来说,这道题还是让我觉得很有意思也很有收获。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值