题目分析

题目链接:5. Longest Palindromic Substring

搜索最佳结果的一个一般思想:先想象这个最佳结果必定具有什么样的特征,然后我们通过这些特征来查找,不断找出很多“候选最佳结果”,最后从这些候选结果中选拔出最佳的结果。

很容易想到,每个回文都有一个“中心”,当回文字符数为基数时,中间的那个字符就是回文中心;但是当回文的字符数为偶数时,回文的中心是最中间的那两个字符,且这两个字符相同

因此要找到回文,我们要先找到“中心”。对任意一个字符或者相同的两个连续字符,我们都可以假设它为回文的“中心”,向它的左右两边扩展出尽可能长的回文。对于每种假设,我们都能得到一个回文,而最长回文必定由其中的某个假设中得到!
因此解题的思路就清晰了:

  1. 枚举所有假设,求出对应的假设结果(扩展出一段回文)
  2. 看看哪个假设的结果长度最大。

前一步是“根据结果应有的特征来搜索候选最佳结果”,后一步是“从所有的候选最佳中选拔出真正的最佳结果”。

代码实现

class Solution {
public:
    
    // 效果:根据输入的中心,向两边扩展出回文
    // 输入:left指向中心的左端,right指向中心的右端
    void expand(const string &str, int &left, int &right) {
        while (left > 0 && right < str.length()-1 && str[left-1] == str[right+1]) {
            --left;
            ++right;
        }
    }
    
    string longestPalindrome(string s) {
        int stringLen = s.length(), maxLeft = 0, maxLen = 0, left = 0, right = 0;
        
        if (stringLen <= 1) return s;
        
        for (int i = 0; i < stringLen; i++) {
            if ((stringLen - i)*2-1 <= maxLen) break;   // 提前结束的条件,后面不可能再出现更长的回文
            
            // 如果i是【最佳结果】的中心左端点,那么中心右端点要么是i,要么是i+1(此时中心两字符必定相等)
            // 这里我们利用了【结果应有的特征】来搜索候选最佳结果
            left = right = i;
            expand(s, left, right);
            if (right-left+1 > maxLen) {
                maxLeft = left;
                maxLen = right-left+1;
            }
            
            left = i;
            right = i+1;
            if (s[left] == s[right]) {
                expand(s, left, right);
                if (right-left+1 > maxLen) {
                    maxLeft = left;
                    maxLen = right-left+1;
                }
            }
        }
        
        
        return s.substr(maxLeft, maxLen);
    }
};

实际上,我们还可以更好地利用【最佳结果】假设,得到更强的筛选条件,从而减少“候选最佳结果”的数量,进而减少搜索时间。

class Solution {
public:
    
    // 效果:根据输入的中心,向两边扩展出回文
    // 输入:left指向中心的左端,right指向中心的右端
    void expand(const string &str, int &left, int &right) {
        while (left > 0 && right < str.length()-1 && str[left-1] == str[right+1]) {
            --left;
            ++right;
        }
    }
    
    string longestPalindrome(string s) {
        int stringLen = s.length(), maxLeft = 0, maxLen = 0, left = 0, right = 0;
        
        if (stringLen <= 1) return s;
        
        for (int i = 0; i < stringLen;) {
            if ((stringLen - i)*2-1 <= maxLen) break;   // 提前结束的条件,后面不可能再出现更长的回文
            
            // 我们还可以这样定义回文的中心:由1个或多个相同字符组成、位置处于回文正中心。比如abccccba中cccc就是中心。
            // 如果i是【最佳结果】的中心左端点,那么只要右边出现相同字符,我们就不断右移right,这样得到回文的中心
            // 这里我们利用了【结果应有的特征】来搜索候选最佳结果,利用这种特征来搜索更加简洁快速
            left = right = i;
            while (right+1 < stringLen && s[right+1] == s[right]) right++;
            
            // 根据新的回文中心的定义,对于abccccba,回文中心的left只能是最左边的c,而不能是第二个c
            // 因此我们没必要再去假设第二个c是回文中心的left,从而减少了假设的数量和搜索的时间
            i = right+1;
            
            expand(s, left, right);
            if (right-left+1 > maxLen) {
                maxLeft = left;
                maxLen = right-left+1;
            }
        }
        
        return s.substr(maxLeft, maxLen);
    }
};

时间复杂度:O(n^2),其中n为输入字符串的最大长度。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.