[C++]LeetCode 5: Longest Palindromic Substring(最长回文子串)

16 篇文章 0 订阅
16 篇文章 1 订阅

Problem:

Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.


分析:

最长回文子串问题也是一个经典算法问题了,前段时间笔试的时候也碰到过,这里就不一一列举暴力法等方法了,以下会简要介绍最终的方法,如果大家想更为深入的了解,我以下也贴上了链接。

Longest Palindromic Substring Part I


以下空间复杂度和时间复杂度均为O(n),主要为Part II的简要翻译。

思路:

复杂度为O(n),即指遍历一遍数组,因而必须创建一数组保存已走过字符的长度信息(之前每个字符的最长回文串)。

同时,如”aba“最长回文长度为3,中心为’b';”aa“最长回文长度为2,中心却是两字符中心,如此在统计长度时,需对奇偶串长分别比较,有什么办法使其统一吗?——插入特殊字符,如‘#’,”aba“为”#a#b#a#“,”aa“为”#a#a#“,如此,奇偶情况的最长长度都分别有了对应的中心。具体如下:


1 、在各个字符间插入特殊字符‘#',将字符S转换为T,如S = “abaaba", T = "#a#b#a#a#b#a#”。

2、定义数组P[length],其中P[i]表示以Ti为中心的最长回文长度的一半(因为T添加了字符’#‘,故其一般即为S中最长回文子串,也即Ti最长边界到Ti的距离)

如:

T = # a # b # a # a # b # a #

P = 0 1 0 3 0 1 6 1 0 3 0 1 0

P中最长的长度为6,而6即为S的最长回文子串。显然,当长度为偶数时,在T中对应的是’#‘;为奇数时,对应的是原有字符,这也就是为何要添加附加字符的原因——统一奇偶情况。


下面问题的关键即为如何得到P数组。


假设现在已经遍历到i = 13的位置,原字符串S的最长回文长度在T中对应位置C = 11,对应原字符串S的长度为9,字符串为”abcbabcba“。C的两边界分别为L = 2,R = 20;i关于C对应的位置为i' = 9。现在如何确定P[i]的大小呢?

图中,i'下方的绿色实线表明了关于i'为中心的左右边界,又i'边界被包含在L、R之间,故i下方的绿色实线区域也一定是对称的。(即i'、i以C对称,i'为中心的长度边缘小于边界L、R,则P[i] = P[i']),令P[i] = p[i'] = 1。


下面讨论另一种情况(即i'、i以C对称,i'为中心的长度边缘超出边界L或R),如下图


假设现在已经遍历到i = 15的位置,原字符串S的最长回文长度在T中对应位置C = 11,对应原字符串S的长度为9,字符串为”abcbabcba“。C的两边界分别为L = 2,R = 20;i关于C对应的位置为i' = 7。

图中绿色实线表示i、i'为中心在边界L、R内一定对称的区域;红色实线表示超出边界L、R可能无法保证i对称的区域;绿色虚线表示横跨C的区域。

显然,我们只能判定绿色实线部分一定对称,即P[i] >= 5,但余下部分是否匹配我们则需要一一比较。

综上,得出以下结论:

if P[ i’ ] ≤ R – i,
then P[ i ] ← P[ i’ ]
else P[ i ] ≥ P[ i’ ]. (Which we have to expand past the right edge (R) to find P[ i ].

剩下一步更新C的位置则变得容易,当当前位置i的边界超过原有R则需要更新,即

if P[i] + i > R

  C = i;

  R = i + P[i].


AC Code(C++):

class Solution {
public:
    //88 / 88 test cases passed.
    //Runtime: 18 ms
    
    string longestPalindrome(string s) {
        if (s.size() < 2) {
            return s;
        }
        
        //将字符串变为需要的形式
        string pStr;
        for (int i = 0; i < s.size(); ++i) {
            pStr.push_back('#');
            pStr.push_back(s[i]);
        }
        pStr.push_back('#');
        
        //寻找回文
        int length = (int)pStr.size();
        int *arrIndex = new int[length];//每个下标对应的回文长度,P
        int mid = 0;//回文子串的中心位置,C
        int mx = 0;//回文子串的边界,R
        int maxIndex = 0;
        int maxLength = 0;
        
        for (int i = 0; i < length; ++i) {
            arrIndex[i] = 0;
            int mirror = (mid << 1) - i;//i关于中心的对称位置,i'
            if (mx > i) {//是否超出边界
                arrIndex[i] = (arrIndex[mirror] < mx - i) ? arrIndex[i] : mx - i;
            }
            while ((i + arrIndex[i] + 1) < length && (i - arrIndex[i] - 1) >= 0 && pStr[i + arrIndex[i] + 1] == pStr[i - arrIndex[i] - 1]) {
                ++arrIndex[i];
            }
            if (arrIndex[i] + i > mx) {//如果边缘超出了当前,需要更新
                mid = i;
                mx = i + arrIndex[i];
            }
            if (arrIndex[i] > maxLength) {
                maxLength = arrIndex[i];
                maxIndex = (i - 1) >> 1;
            }
        }
        delete[] arrIndex;
        
        string outputStr(s, maxIndex - (maxLength - 1)/2, maxLength);
        return outputStr;
    }
};


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值