zoukankan      html  css  js  c++  java
  • [LeetCode] 2 Longest Palindromic Substring 最长回文子串

    [LeetCode]2 Longest Palindromic Substring 最长回文子串

    Description

    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.

    Example

    Example 1:
    Input: "babad"
    Output: "bab"
    Note: "aba" is also a valid answer.
    
    Example 2:
    Input: "cbbd"
    Output: "bb"
    

    题意

    给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。存在唯一的最长回文子串。

    题解

    这道题目是要求输出最长回文子串,利用Manacher's Algorithm可以将时间复杂度提升到O(n)的惊人水平。下面首先介绍下该算法:

    第一步,先对字符串进行一步预处理,做法是在每个字符的两侧添加数据中不会出现的符号(例如:#),"bob" --> "#b#o#b#" ,"noon" --> "#n#o#o#n#"。
    这样做的目的是无论给出的字符串的长度的奇偶性,处理后得到的字符串都是奇数个,这样就可以不用讨论奇偶性的问题了。

    第二步,我们需要处理和新字符串s等长的数组p,其中p[i]表示以s[i]字符为中心的回文子串的半径,若p[i] = 1,则该回文子串就是s[i]本身,一个简单的例子:

    s[i]: # 1 # 2 # 2 # 1 # 2 # 2 #
    p[i]: 1 2 1 2 5 2 1 6 1 2 3 2 1
    

    为什么需要关注回文子串的半径呢?以上面的例子中中间的1为例,其回文半径为6,而未添加#号的回文子串为 "22122",长度是5,为回文半径减1。原因很简单,可以分两种情况考虑,如果回文串长度为奇数,那么单侧的半径是(长度+1)/2,又因为单侧填充字符,所以填充字符后的回文半径为((长度+1)/2)x2=长度+1,如果长度为偶数,那么单侧的半径是长度/2,又因为两侧填充字符,所以填充字符后的回文半径为(长度/2)x2+1=长度+1

    然后看中间的 '1' 在字符串 "#1#2#2#1#2#2#" 中的位置是7,而半径是6,7-6=1,刚好就是回文子串 "22122" 在原串 "122122" 中的起始位置1。那么我们再来验证下 "bob","o" 在 "#b#o#b#" 中的位置是3,但是半径是4,3-4=-1,出现了错误。所以我们应该至少把中心位置向后移动一位,才能得到正确的解,我们需要在前面增加一个字符,这个字符不能是#号,也不能是s中可能出现的字符,所以一般会用$添加到开头。这样的话,只要拥有p数组,我们就可以得到最长回文子串的长度和起始位置。

    第三步,我们要介绍如何求取数组p。首先我们需要增加两个辅助变量,id和mx。其中id是指能延申到最右端位置的那个回文串的中心位置,mx是回文串能够延申到的最右端位置。
    然后算法的核心是:
    如果 mx > i, 则 p[i] = min( p[2 * id - i] , mx - i )
    否则,p[i] = 1
    j=2*id-i,表示i以id对称的点 ,因为 j 到 id 之间到距离等于 id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i。

    if(mx>i):

    当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],其中 j = 2*id - 1。


    当 mx - i<=P[j] 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,上图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] = mx - i。至于mx之后的部分是否对称,就只能去匹配了,这就是后面紧跟到while循环的作用。

    else:
    对于 mx <= i 的情况,此时镜像对预判位置起不到作用,只能从长度为1开始对比,所以p[i]=1

    以上就是Manacher's Algorithm的全部内容。

    对于本题而言要求输出最长回文子串,利用Manacher's Algorithm处理字符串,中途保留记录最大的p[i]值已经对应的索引位置i。将字符串拆分即可。

    代码

    class Solution {
    public:
        string longestPalindrome(string s) {
            string str = "$#";
            int le = s.size();
            for(int i=0;i<le;i++){
                str+=s[i];
                str+='#';
            }
            int len = str.size();
            int P[len]={0},id=0,mx=0,resMax=0,resId=0;
            for(int i=1;i<len;i++){
                if(mx>i){
                    P[i]=min(P[2*id-i],mx-i);
                }else{
                    P[i]=1;
                }
                while(str[i+P[i]]==str[i-P[i]]) ++P[i];
                if(mx<i+P[i]){
                    mx = i + P[i];
                    id = i;
                }
                if (resMax< P[i]) {
                    resMax = P[i];
                    resId = i;
                }
            }
            return s.substr((resId - resMax) / 2, resMax - 1);
        }
    };
    
  • 相关阅读:
    索引使用及注意事项
    Explain详解与索引
    JVM常量池了解
    认识Mysql索引
    JVM调优工具及了解
    JVM垃圾收集器
    JVM垃圾回收相关算法
    JVM字节码文件结构剖析
    JVM对象创建与内存分配机制
    JVM内存参数设置
  • 原文地址:https://www.cnblogs.com/caomingpei/p/10612847.html
Copyright © 2011-2022 走看看