LeetCode No.5 Longest Palindromic Substring

说实话,看到回文的题,我的本能是拒绝的...更何况还是一道最长回文子串的问题!

哈哈,虽然不喜欢这类题,但是我们不能就此放弃,言归正传,先来看一下题目:

给定一个字符串s,找到其中最长的回文子串,假设s的长度不超过1000。

唔,这个假设非常的耐人寻味,为什么要限定s的长度在1000以内呢,我猜想解决这道题的算法的时间复杂度可能很难降到很低,估计很难在O(n)的时间内完成!


一、首先我们来看一下什么是回文Palindrom

回文就是正着读和反着读完全一样的序列,比如:

"abccba", "aaaaaaa", "aaabaaa", "asdfghjkllkjhgfdsa"等等,我们可以发现,这些字符串,无论你从左往右读还是从右往左读,内容是一样的!这就是回文序列。

回文序列既可以有奇数个字符,也可以有偶数个字符,偶数个字符的回文序列最中间的两个字符一定是一样的;

回文序列左右两端各去掉一个字符,剩下的序列也一定还是一个回文序列!这是一个非常有用的性质,我们一会就会看到,它对我们的算法而言很有帮助。

那么回到我们的问题,如何找到字符串s中最长的那个回文子串呢?

  1. 找出所有回文子串;
  2. 在这些子串中找出最长的;
嗯,没问题,虽然这看上去很麻烦,不过这也是我们思路的起点,首先我们来思考如何判断一个子串是不是回文;


二、判断回文子串

最直接的思路就是直接按照定义,我们求出一个字符串的逆字符串,然后对比它与原字符串是否相同,如果是,那这就是一个回文序列,否则不是。那么反转一个字符串并比对要花去O(n)的时间,那么找出一个字符串的所有子串将花去O(n^2)的时间,那么这道题的总体复杂度将达到O(n^3),太久了,显然这种直接的思路并不适合我们这道题,我们需要另辟蹊径!

回想一下我们之前提到过的那个很有用的性质:回文序列左右两端各去掉一个字符,剩下的序列也一定还是一个回文序列。反过来想一下,如果我们已经有了一个回文序列,那么一旦它前后各一个字符是相同的,那么原回文序列加上前后字符将构成一个新的长度加2的回文序列,比如:对于回文序列 "abba",如果我们发现该序列的前一个字符是'c',后一个也是'c',那么"cabbac"就是那个新的长度加2的回文序列。怎么样,聪明如你是不是已经有思路了~没错,我们可以通过不断的向两边扩张一个范围来判断新的子串是不是回文!那么我们该怎么开始呢,别担心,继续想一下下,最短的回文序列(除了空序列之外)是什么呢,没错,就是仅由一个字符构成的序列!不过这只是针对于包含奇数个字符的回文序列,偶数个的呢?那就是由两个相同字符组成的字符序列呗~真是小菜一碟!用这种方法,最坏情况下也可以把时间复杂度下降到O(n^2)。


三、本题解法

  1. 依次遍历字符串中的每一个字符,如果出现后一个字符和当前字符相同,则向右扩张我们的判断范围直到出现不同字符;
  2. 判断当前子串范围的前一个字符和后一个字符是否相同,如果相同,就同时向左右扩张一个字符,如果不同,则递进到以下一个字符为中心的位置重新开始扩张判断;
  3. 每次迭代都会更新当前最长的回文长度;
来看一下代码:

class Solution {
public:
    string longestPalindrome(string s)
    {
        //子串起始位置和长度
        int start = 0, max_len = 0;
        
        for(int i = 0; i < s.size();)
        {
            int j = i, k = i;
            //判断重复字符,包含重复字符的子串就是回文序列
            while(k < s.size() - 1 && s[k + 1] == s[k]) ++k;
            //i可以直接递进到一连串重复字符的后一个位置
            i = k + 1;
            //像左右扩张
            while(j > 0 && k < s.size() - 1 && s[j - 1] == s[k + 1]) ++k, --j;
            //计算当前回文子串的长度
            int len = k - j + 1;
            //与当前最大长度对比,更新最大回文子串长度及其起始位置
            if(len > max_len)
            {
                start = j;
                max_len = len;
            }
        }
        return s.substr(start, max_len);
    }
};

这里面有个小细节需要注意,对于一连串的重复字符,无论有奇数个还是偶数个,它们都组成了一个回文序列,而且在出现这样一连串的重复字符时,我们可以直接将 i 递进到后面第一个不重复的位置,这是为什么呢?我们来观察一下这种回文:
"aaaaaaaa",现在我们找到了一个这样的子串,并且该子串两边的一个字符都不是'a',这时候我们将 i 放在该子串的任何一个位置处,向两边扩张,所能得到的回文子串都不会比该子串长!那么对于一般的回文序列来说是不是也有这种性质呢?
"ababa",来看这个例子,当我们的 i 为1时,也就是说判断点指向了第一个'b',这时候我们向两边扩张,发现了一个回文子串"aba",这是否代表以该子串中的最后一个字符'a'为中心扩张,不会出现比"aba"还要长的回文子串呢,显然不是!这个'a'还偏偏就能扩张出一个更长的回文子串"ababa"!所以只有在出现连续重复字符的时候,我们才能把判断位置直接跳跃到子串的后一个字符处,而对于一般的回文子串来说,我们只能老老实实地一步一个脚印的判断~

下面是运行结果,可以看出花费的时间是很难达到最坏情况下的O(n^2)的,这样的字符串很特殊~



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值