KT学算法(三)——最长回文子串与Manacher算法

题目来源:

https://leetcode.com/problems/longest-palindromic-substring/

解题思路:

方法一:

暴力查找:由于找到字符串的所有字串的时间复杂度为O(n^2),判断一个字符串是否为回文串需要遍历,时间复杂度为O(n),所以这个算法的时间复杂度为O(n^3)

方法二:

对于任意一个长度为n的字符串,总是存在最长的回文字串,对于这个回文字串而言,它的中心只有2n-1种可能存在的位置。它可以位于某一个字符上(如”aba”的回文中心在字符b上),这一共有n种情况。回文中心也可以在两个字符的缝隙中(如”aa”的回文中心在两个字符a之间),这一共有n-1种情况。

因此我们可以考虑这2n-1个位置上,对应的最大回文子串长度。如果不是回文串(如”ab”这种情况),那么最大回文子串长度就是0。这个过程的时间复杂度为O(n),由于需要遍历这2n-1个位置,整个算法的时间复杂度为O(n^2)。

我在做此题时,为了偷懒,把两种可能合并起来了,结果反而导致代码量大幅度提升。不过修修补补也通过了测试,代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        int longestLength = 1, startPos = 0;
        for(int i = 0; i < s.size(); i++){
            for(int j = i - 1, tempLength = 1, flag = 0; j > -1; --j){
                if(2 * i - j - flag < s.size()){
                    if(s[j] == s[2 * i - j - flag]){
                        tempLength += 2;
                        if((j == 0 || 2 * i - j - flag == s.size() - 1) && tempLength >= longestLength){
                            longestLength = tempLength;
                            startPos = j;
                        }
                    }
                    else if(s[j] == s[i] && s.substr(j, i-j+1) == string(i-j+1, s[j])){
                        ++tempLength;
                        ++flag;
                        if(tempLength > longestLength){
                            longestLength = tempLength;
                            startPos = j;
                        }
                    }
                    else{
                        if(tempLength > longestLength){
                            longestLength = tempLength;
                            startPos = j + 1;
                        }
                        break;
                    }
                }
                else if(s[j] == s[i] && tempLength == longestLength){
                    ++longestLength;
                    startPos = j;
                    break;
                }
                else{
                    break;
                }
            }
        }
        return s.substr(startPos, longestLength);
    }
};

方法三:Manacher算法

刚刚那个算法其实还可以继续优化,关键在于,遍历这2n-1个位置时,每次都要重新计算最大回文子串长度,但由于回文串的对称性,有些计算其实是不必要的。

首先把字符串用一个特殊符号,比如#填充。比如字符串S = “abaaba”,填充后的新字符串T = “#a#b#a#a#b#a#”

观察T的每个位置上的最长回文子串长度P,我们发现一个规律:

T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0

即这个数组P也是对称的。

但是这个规律并不总是成立,假设当前正在计算的回文子串的中心为C,比如在上一个例子中C = 6,即字符串中心的那个#。对于第i个字符串,记i’ = 2C - i,即i’ 是 i 关于中心C对称的点。只有当p[i’] + i 不超过这个回文串最右边边界时,p[i]才等于p[i’],否则p[i] >= p [i’],此时我们需要拓展这个回文子串。新的中心C = i,并重新计算最右边的边界。

这种方法的巧妙之处在于,对于p[i]的计算,不必重新向两端遍历,而是充分利用了之前的计算结果,只有在无法利用计算结果时,才会选择向右拓展。虽然依然需要O(n)的时间复杂度来遍历2n-1个位置,但是在计算每个位置的最大回文子串长度时,要么根据对称性直接得出结果,时间复杂度为O(1),要么向右拓展,进行循环。但是虽然这里进行了内部循环,但是由于总是向右拓展,总循环次数一定不会超过n,所以整个算法的的时间复杂度为O(n)+ O(n) = O(n)。

Manacher算法非常巧妙的利用了对称性减少计算量,不过也不容易想到,实现起来也有一些细节需要考虑。如果算法对时间要求非常高,可以选择这个算法

你可能感兴趣的:(算法)