• leetcode——Longest Palindromic Substring


    题目: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.

    大意是:从所给的字符串s中找出其最长回文字串

    例如:s = “aabcba”, 则最长回文字串为”abcba”

    思路1:

        我一开始的思路:遍历s,找出两个相同字母之间的最长回文字串,因此要记录后一个字母的位置,将其存到一个map里。但是还是超时

    class Solution {
    public:
        string longestPalindrome(string s) {
            // 用map记录每个字母的位置
            // 遍历s,找出后面同一个字母的位置,看其之间是否是回文
            unordered_map<char, vector<int> > index;
            for(int i = s.size()-1; i >= 0; i--){
                char c = s[i];
                vector<int> tmp;
                if(index.find(c) != index.end()) tmp = index[c];
                tmp.push_back(i);
                index[c] = tmp;
            }
            int maxRet(1);
            string maxSub(""+s[0]);
            for(int i = 0; i != s.size(); i ++){
                if(s.size() - i <= maxRet) break;
                char c = s[i];
                auto tmp = index[c];
                for(int j: tmp){ // i后面的同一个字符的索引
                    if(j > i){
                        // 如果字符串本身长度都没maxRet大,则不用判断其是否是回文
                        if(j-i+1 <= maxRet) break; 
                        int count(0);
                        int k = i;
                        while(c == s[j] && k < j){
                            count+=2;
                            k++;
                            j--;
                        }
                        if(k >= j){  // 是回文字串
                            // 说明是回文字符串且中间有单个字符
                            if(k == j) count--;
                            if(maxRet < count){
                                maxRet = count;
                                maxSub = s.substr(i, count);
                            }
                        }
                    }
                }
            }
            return maxSub;
        }
    };

    image

    思路2:

    后来看了网上的答案,用到Manaer’s Algorithm,时间复杂度是O(n), 参考了博文之后自己写了一遍,大致了解了算法的过程。

    算法的基本思想是要把s=”babcbabcbaccba”变为T=”#b#a#b#c#b#a#b#c#b#a#c#c#b#a#”,之后在T的基础上进行查找。

    如图,把s=”babcbabcbaccba”变为T之后, #的作用是不管s的长度是奇数还是偶数,使得以T[i]为中心的回文字符串都是奇数;

    p[i]是以T[i]为中心的回文字符串的长度;

    C是最近一次算出的最长回文字符串的中心字符的位置;

    R是以C为中心的回文字符串的最右边的字符的位置,同理,L是最左边的位置,则L到R之间是以C为中心的回文字符串。

    上图的意思是当i==15时,i属于以T[C]为中心的、左右两边长为9的回文字符串的范围内。

    p[i]的这个长度最终由下面算出来的

    while(T[i+1+p[i]] == T[i-1-p[i]]){
       p[i]++;
    }

    当i==0时,T[0]之前没有回文字串,因此p[0]是0,然后T[0]的左边T[-1]和右边T[1]不是同样的字符,所以p[i]还是0;

    当i==1时,T[1]左右有T[0]==T[2],因此p[1]++,因此p[1]为1;

    以此类推,T[3]即a的左边有3个和3个右边的字符相等,所以p[3]为3

    上述p[i]的算法是扩充,初始的p[i]是下面算出来的,由初始到扩充的这样的好处是优化了时间复杂度是O(n²)的直接遍历s[i]算出以其为中心的回文字符串长度的那种算法:

    int i_mirror = 2*C - i; // i` = C – (i-C)
     p[i] = R>i ? min(p[i_mirror], R-i) : 0;

    这个的意思是

    1. 若前面算出的以T[C]为中心的回文字符串很长,当前T[i]属于其回文中心内,即R>i,则可以找到其在回文字符串中对应的i`的p[i`],由其算出p[i], 例如,T[11]的回文串长度为9,则R则到了20,那么算T[15]时,则有两种情况
        那么其p[i]:
            1)可能就是其前面镜像i`的p[i`]的长度,例如T[12]的b则和T[10]的b是回文,则p[12]和p[10]的相等,
            2)可能是R-i的长度,但是以T[i`]为中心的回文字符串可能不只在T[C]的回文字符串的范围内,例如
        因此p[i]应为1)和2)中最小的情况,之后再以T[i]为中心重新扩充p[i]
    2. 若T[i]不在以T[C]为中心的回文字符串内,则其p[i]为0

    每次扩充完p[i]之后,以T[i]为中心的回文字符串的R可能就要超出以T[C]为中心的字符串的R了,因此要更新C和R,这样能保证算出i的镜像i`的位置是当前回文字符串中算出来的

    if(i+p[i] > R){
        C = i;
        R = i + p[i];
     }

    之后找出T中p[i]最大值maxVal以及最大值的位置index,这样能用index算出s中回文字符串的开始位置,即(index-maxVal)/2,例如上图是(11-9)/2 = 1;

    因此s=”babcbabcbaccba”,即从s[1]的a开始,长度为9:“abcbabcba”

    下面是完整代码,注意头加入’^’尾加入’$’作为开始和结束的标志,因此遍历T是是[1, n-1),而最后算回文字符串开始位置时要多-1,减去开始’^’的占位

    class Solution {
        // 将s="aba"变为T="^#a#b#a#&",其中"^$"为字符串开始和结束符,
        // #的作用是不管s的长度是奇数还是偶数,使得以T[i]为中心的回文字符串都是奇数
        // 其中T[i]可能是字母也可能是'#'
    public:
        string longestPalindrome(string s) {
            // 将s变为T
            string T = S2T(s);
            // 计算以T[i]为中心的最大回文字符串的长度p[i]
            const int n = T.size();
            // C是最近一次算出的最长回文字符串的中心字符的位置
            // R是最近一次算出的最长回文字符串的最右边的字符的位置
            int p[n], C(0), R(0); 
            // 1. 若前面算出的以T[C]为中心的回文字符串很长,当前T[i]属于其回文中心内,即R>i,
            // 那么其p[i]:(如上图)
            //  1)可能就是其前面镜像i`的p[i`]的长度
            //  2)可能是R-i的长度,但是以T[i`]为中心的回文字符串可能不只在T[C]的回文字符串的范围内
            //  因此p[i]应为1)和2)中最小的情况,之后再以T[i]为中心重新扩充p[i]
            // 2. 若T[i]不在以T[C]为中心的回文字符串内,则其p[i]为0
            for(int i = 1; i < n-1; i ++){
                int i_mirror = 2*C - i; // i` = C - (i-C)
                p[i] = R>i ? min(p[i_mirror], R-i) : 0;
                // 扩充p[i]
                while(T[i+1+p[i]] == T[i-1-p[i]]){
                    p[i]++;
                }
                // 若以T[i]为中心的字符串的范围走出了以T[C]为中心的字符串的范围,则更新C,R
                if(i+p[i] > R){
                    C = i;
                    R = i + p[i];
                }
            }
            // 算好p[i]之后找出最大的p[i]所在的i的位置index,则算出回文字符串在原s中的开始字符的位置
            // 之后得出其最大回文子串
            int maxVal(0), index(0);
            for(int i = 1; i < n-1; i ++){
                if(maxVal < p[i]){
                    maxVal = p[i];
                    index = i;
                }
            }
            return s.substr((index-1-maxVal)/2, maxVal);
            
        }
        string S2T(const string &s){
            if(s.size() == 0) return "^$";
            string T = "^";
            for(int i = 0; i != s.size(); i ++){
                T += "#" + s.substr(i, 1);
            }
            T += "#$";
            return T;
        }
    };
  • 相关阅读:
    memcached源码剖析5:并发模型
    memcached源码剖析4:并发模型
    深入剖析php执行原理(4):函数的调用
    深入剖析php执行原理(2):函数的编译
    剖析php脚本的超时机制
    strerror的坑
    深入理解php中的ini配置(2)
    深入理解php中的ini配置(1)
    一个“日期”字符串进行比较的case
    用valgrind检测php扩展内存泄露
  • 原文地址:https://www.cnblogs.com/skysand/p/4289763.html
Copyright © 2020-2023  润新知