5. Longest Palindromic Substring - LeetCode

Longest Palindromic Substring(字符串)

给定一个字符串s,找出s中最长的回文子字符串。
在这里插入图片描述
以每一个字符为中心,向两边扩散寻找回文串,算法的时间复杂度O(n*n),可以通过OJ,要注意奇偶情况。由于回文串的长度可奇可偶,比如"bob"是奇数形式的回文,"noon"就是偶数形式的回文,两种形式的回文都要搜索。对于奇数形式的,从遍历到的位置为中心,向两边进行扩散;对于偶数情况,就把当前位置和下一个位置当作偶数回文的最中间两个字符,然后向两边进行搜索,代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.size() < 2) return s;
        int n = s.size(), maxLen = 0, start = 0;
        for (int i = 0; i < n - 1; i++) {
            searchPalindrome(s, i, i, start, maxLen);//搜索长度奇数的回文串
            searchPalindrome(s, i, i + 1, start, maxLen);//搜索长度偶数的回文串
        }
        return s.substr(start, maxLen);
    }
    void searchPalindrome(string s, int left, int right, int& start, int& maxLen) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            left--;
            right++;
        }
        if (maxLen < right - left - 1) {
            start = left + 1;
            maxLen = right - left - 1;
        }
    }
};

可以不使用子函数直接在一个函数中实现,还是要定义两个变量start和maxLen,分别表示最长回文子串的起点跟长度。遍历s中的字符时,首先判断剩余的字符数是否小于等于maxLen的一半,是则说明maxLen无法再变长了,直接break掉。否则就要继续判断,用两个变量left和right分别指向当前位置,然后向右遍历跳过重复项,这个操作很必要,比如对于 noon,i在第一个o的位置,如果我们以o为最中心往两边扩散,是无法得到长度为4的回文串的,只有先跳过重复,此时left指向第一个o,right指向第二个o,然后再向两边扩散。而对于 bob,i在第一个o的位置时,无法向右跳过重复,此时left和right同时指向o,再向两边扩散也是正确的。所以可以同时处理奇数和偶数的回文串,之后的操作就是更新maxLen和start,跟上面的操作一样,代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.size() < 2) return s;
        int n = s.size(), maxLen = 0, start = 0;
        for (int i = 0; i < n;) {
            if (n - i <= maxLen / 2) break;
            int left = i, right = i;
            while (right < n - 1 && s[right + 1] == s[right]) right++;
            i = right + 1;
            while (right < n - 1 && left > 0 && s[right + 1] == s[left - 1]) {
                left--;
                right++;
            }
            if (maxLen < right - left + 1) {
                maxLen = right - left + 1;
                start = left;
            }
        }
        return s.substr(start, maxLen);
    }
};

此题还可以用动态规划Dynamic Programming来解,维护一个二维数组dp,其中dp[i][j]表示字符串区间[i, j]是否为回文串,当i = j时,只有一个字符,肯定是回文串,如果i = j + 1,说明是相邻字符,此时需要判断s[i]是否等于s[j],如果i和j不相邻,即i - j >= 2时,除了判断s[i]和s[j]相等之外,dp[j + 1][i - 1]若为真,就是回文串,通过以上分析,可以写出递推式如下:

   dp[i, j]   = 1                                          ,  if i == j
              = s[i] == s[j]                              ,  if j = i + 1
              = s[i] == s[j] && dp[i + 1][j - 1]          ,  if j > i + 1 

代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.empty()) return "";
        int dp[s.size()][s.size()] = {0}, left = 0, right = 0, len = 0;
        for (int i = 0; i < s.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1]));
                if (dp[j][i] && len < i - j + 1) {
                    len = i - j + 1;
                    left = j;
                    right = i;
                }
            }
            dp[i][i] = 1;
        }
        return s.substr(left, right - left + 1);
    }
};

最后的解法采用马拉车算法Manacher’s Algorithm,这个算法将时间复杂度提升到了O(n),算法本身也设计的很巧妙,代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        string t ="$#";
        for (int i = 0; i < s.size(); ++i) {
            t += s[i];
            t += '#';
        }
        int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0;
        for (int i = 0; i < t.size(); ++i) {
            p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
            while (t[i + p[i]] == t[i - p[i]]) ++p[i];
            if (mx < i + p[i]) {
                mx = i + p[i];
                id = i;
            }
            if (resMx < p[i]) {
                resMx = p[i];
                resId = i;
            }
        }
        return s.substr((resId - resMx) / 2, resMx - 1);
    }
};

参考博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值