字符串系列6 最长回文子串

LeetCode有一道最长回文子串的题,下面基本是官方解答的翻译版。

方法1:最长公共子串

常见错误

一些人可能会快速想到一个解决方法,然而这个方法却是错误的(但是很容易将它修改成正确的):

S S S 反转成 S ′ S^\prime S,然后找到 S S S S ′ S^\prime S 的最长公共子串,这个最长公共子串就是 S S S 的最长回文子串。

这看起来是没有问题的,比如 S = c a b a S = caba S=caba S ′ = a b a c S^\prime = abac S=abac 的最长公共子串 aba 就是正确答案。
但是对于 S = a b a c d f g d c a b a S = abacdfgdcaba S=abacdfgdcaba S ′ = a b a c d g f d c a b a S^\prime = abacdgfdcaba S=abacdgfdcaba 的最长公共子串 abacd 就显然不是一个回文子串。

正确算法

我们可以看到,当 S S S 的某一个非回文子串正好是另一子串的反转,最长公共子串方法就会失败。为了纠正这个问题,每次我们找到一个最长的公共子串候选者时,我们检查子串的索引是否与被反转子串的原始索引相同。如果是,那么我们尝试更新到目前为止发现的最长的回文;如果不是,我们就跳过这个,找到下一个候选子串。

这给了我们一个时间复杂度 O ( n 2 ) O(n^2) O(n2) ,空间复杂度 O ( n 2 ) O(n^2) O(n2) (空间复杂度可以改进到 O ( n ) O(n) O(n))的动态规划解决方案。更多信息参考:
维基百科:Longest common substring problem 和我的博客:字符串系列3 最长公共子串。

方法2:暴力

列举所有子串,然后查看子串是否是回文串,不说了。

时间复杂度为 O ( n 3 ) O(n^3) O(n3),空间复杂度为 O ( 1 ) O(1) O(1),空间复杂度可进一步改进。

方法3:动态规划

为了改进暴力搜索,我们首先观察如何在验证回文时避免不必要的重新计算。以“ababa”为例。如果我们已经知道bab是一个回文,很明显ababa必须是回文,因为两个左右两边的字母是相同的。

我们将 P ( i , j ) P(i,j) P(i,j) 定义为:
P ( i , j ) = { t r u e , i f   t h e   s u b s t r i n g   S i   . . .   S j i s   a   p a l i n d r o m e f a l s e , o t h e r w i s e . P(i,j)=\left\{ \begin{aligned} &true, & if\ the\ substring\ S_i\ ...\ S_j is\ a\ palindrome\\ &false, & otherwise. \end{aligned} \right. P(i,j)={true,false,if the substring Si ... Sjis a palindromeotherwise.
因此, P ( i , j ) = ( P ( i + 1 , j − 1 )   a n d   S i = = S j ) P(i,j)=(P(i+1,j-1)\ and \ S_i ==S_j) P(i,j)=(P(i+1,j1) and Si==Sj)
初始条件为: P ( i , i ) = t r u e P(i,i)=true P(i,i)=true P ( i , i + 1 ) = ( S i = = S i + 1 ) P(i,i+1)=(S_i==S_{i+1}) P(i,i+1)=(Si==Si+1) 然后我们就可以使用动态规划解决问题,我们首先初始化一个和两个字母的回文,然后逐步找到所有三个字母回文,以此类推下去。

DP的时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度可进一步改进。

方法4:从回文串中心展开

事实上,我们可以用 O ( n 2 ) O(n^2) O(n2)的时间复杂度和 O ( 1 ) O(1) O(1) 的空间复杂度解决问题。

回文串是中心对称的,因此,回文可以从其中心扩展,并且只有 2 n − 1 2n-1 2n1 个这样的中心。

你可能会问为什么有 2 n − 1 2n-1 2n1 个而不是 n n n 个中心?原因是回文的中心不止可以在字母上,也可以在两个字母之间,这些回文具有偶数个字母,例如abba,其中心位于两个b之间,Java 代码如下:

public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);
        int len2 = expandAroundCenter(s, i, i + 1);
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
    int L = left, R = right;
    while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
        L--;
        R++;
    }
    return R - L - 1;
}

方法5:Manacher算法

这个算法在我之前的博客里面写过,附上链接:字符串系列2 Manacher 算法

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