Leetcode005. Longest Palindromic Substring

来源:互联网 发布:天刀捏脸数据金木研 编辑:程序博客网 时间:2024/05/29 07:17

题目描述:

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example:

Input: "babad"Output: "bab"Note: "aba" is also a valid answer.

Example:

Input: "cbbd"Output: "bb"
题目大意:

求字符串的最长回文子串。

思路:

首先此题最暴力的做法当然是枚举所有子串判断是否回文,O(n³)

是否还有更快的方法?

中心扩展法:观察到所有的回文串都相对于中间的字符对称,所以可以枚举中间字符,然后往两边展开,一旦发现两边不等直接break。这样就省去了暴力做法的判断回文那一步。时间复杂度O(n²)

同样地这题也可以用DP来解。f[i][j]表示s[i]到s[j]是否为回文串。则f[i][j]=f[i+1][j-1]&&s[i]==s[j]。时间复杂度O(n²)。

还能更快吗?


这里可以使用解决回文串问题的经典O(n)算法:Manacher马拉车算法

算法的思想来源于对上面中心扩展法的优化。中心扩展法在扩展时没有充分利用已遍历的信息,导致一个字符被多次遍历,增加了时间复杂度。

首先,由于来源于中心扩展法,manacher只能处理长度为单数的回文串,所以在字符串每两个字符中间插入一个特殊字符(例如#)。

定义maxr表示当前枚举过的回文串中最右边的字符位置,pos表示maxr所在的回文串的中心,rl[i]表示以i为中心的回文串能向左或向右扩展多少。

当我们枚举到中心位置i时,有两种情况:

1、i<maxr。此时i处于以pos为中心的那个回文串中。

此时由于回文串两边对称的性质,可以直接把rl[i]置为rl[j],然后再在此基础上继续向两边扩展

2、i>maxr

这时i根本没有被遍历过,所以只能从0开始慢慢扩展。

注意到,每有一个新字符被遍历,maxr一定+1,因为maxr左边的字符不需要再被遍历。所以时间复杂度O(n)。


另外,这道题似乎可以用自动机做,不过本蒟蒻并不会。

不得不吐槽一下,关于字符串的算法经常有一些奇怪的名称:马拉车(Manacher),看毛片(KMP),自动AC机(AC自动机)……

代码:

DP(c++):

class Solution {public:// 动规     string longestPalindrome(string s) {        bool p[2000][2000] = {0}; // p[i][j]表示s[i~j]是否回文         int len = s.size();        string ans = s.substr(0, 1);        for (int i = 0; i < len; i++)        {            p[i][i] = true;            if (i < len - 1 && s[i] == s[i + 1])            {                p[i][i + 1] = true;                ans = s.substr(i, 2);            }        }        for (int i = 3; i <= len; i++) // 先枚举子串长度             for (int j = 0; j <= len - i; j++) // 再枚举子串开始位置                 if (s[j] == s[j + i - 1] && p[j + 1][j + i - 2])                {                    p[j][j + i - 1] = true;                    ans = s.substr(j, i);                }        return ans;    }};
Manacher(python):
class Solution(object):    def longestPalindrome(self, s):        """        :type s: str        :rtype: str        """        s = '$' + '$'.join(s) + '$' # 加特殊字符        rl = [0] * len(s) # rl[i]表示以i为中心的回文串能扩展多远        maxr = pos = maxlen = maxmid = 0        for i in range(len(s)):            if i < maxr:                rl[i] = min(rl[2 * pos - i], maxr - i + 1) # i的回文长度至少等于i关于pos的对称点            else:                rl[i] = 1            while i + rl[i] < len(s) and i - rl[i] >= 0 and s[i + rl[i]] == s[i - rl[i]]:                rl[i] += 1 # 中心扩展            if i + rl[i] - 1 > maxr:                maxr = i + rl[i] - 1 # 更新maxr的值                pos = i            if rl[i] > maxlen:                maxlen = rl[i] # 更新答案                maxmid = i        return s[maxmid - maxlen + 1: maxmid + maxlen].replace('$', '') # 把之前加的特殊字符去掉