关于最长回文字符串

回文字符串:回文字符串既正反都一样的字符串,如a,aba,abccba等

一、如何判断一个字符串是不是回文字符串

  1.栈实现

  将字符串的前一部分依次入栈,再出栈和字符串的后半部分比较(优缺点:需要额外空间占用,复杂度O(n)

  实现代码:

 1public boolean isPalindromeStack(String s) {
 2  char[] sourceStr = s.toCharArray();
 3  Stack<Character> partStr = new Stack<Character>();
 4  int fence=0;
 5  for(fence=0;fence<sourceStr.length/2;fence++)
 6    partStr.push(sourceStr[fence]);
 7 
 8  if(s.length()%2!=0)
 9    fence++;
10   
11  while(fence<sourceStr.length || !partStr.isEmpty()) {
12    char tempC = partStr.pop();
13    if(sourceStr[fence]!=tempC)
14      return false;
15    fence++;
16  }
17  return true;
18}

  2、递归实现

  在比较字符串的首末两个字符后,产生的新问题和旧问题的形式是一样的,可用递归形式求解。(优缺点:问题规模小、不占用额外空间

  实现代码:

    public boolean isPalindromeRecursion(char[] s,int start,int end,int length) {
        if(length==0 || length==1)
            return true;
        if(s[start]!=s[end])
	    return false;

	return isPalindromeRecursion(s,start+1,end-1,length-2);
    }

  3、首尾向中间(中间向首尾)遍历实现

  从字符串的首末开始比较,若start>=end时,比较结束。

  实现代码:

  

    public boolean isPalindromeIndex(char[] s,int length) {
        int start=0,end=length-1;
        while(end>start) {
            if(s[start] != s[end])
                return false;
            start++;
            end--;
        }
        return true;
    }

二、给定最长回文子串

  1、枚举算法(时间复杂度O(n*n*n)

  得到所有子字符串,并依次判断是否是回文字符串。

  

 1 public String longestPalindrome(String s) {
 2         if(s.length() <= 1)
 3             return s;
 4         String resultStr = "";
 5         for(int i=s.length();i>=1;i--) {
 6             for(int j=0;(j+i)<s.length();j++) {
 7                 //子字符串从大到小比较,一旦遇到最大回文子字符串就返回
 8                 if(isPalindromeIndex(s.substring(j, j+i-1).toCharArray(),
 9                         s.substring(j, j+i-1).length()))
10                 {
11                     resultStr=s.substring(j, j+i-1);
12                     return resultStr;
13                 }
14             }
15         }
16         return resultStr;
17     }

  2、朴素算法(时间复杂度O(n*n)

  遍历每个字符,以遍历的字符为中心(需要考虑回文字符子串有奇数还是偶数个字符两种情况),依次向两边比较左右对称的字符,从而查找出以该字符为中心的回文字符串,最终得到最长回文字符串。该算法需要考虑到回文字符串存在奇数个字符和偶数个字符两种情况。因遍历每个字符的时间复杂度为O(n),判断回文的时间复杂度为O(n),所以该算法的时间复杂度为O(n*n).

 1 public String longestPalindrome(String s) {
 2         if(s.length() <=1)
 3             return s;
 4         StringState currentState = new StringState(s.toCharArray());
 5         int tempStart=0,tempEnd=0;
 6         for(int i=0;i<s.length();i++) {
 7             tempStart = i-1;
 8             tempEnd = i+1;
 9 //奇数时
10             checkPalindrome(currentState,tempStart,tempEnd);
11             tempStart = i;
12 //偶数时
13             checkPalindrome(currentState,tempStart,tempEnd);
14         }
15         if(currentState.start == currentState.end)
16             return null;
17         return s.substring(currentState.start, currentState.end+1);
18     }
19     private void checkPalindrome(StringState currentState, int tempStart, int tempEnd) {
20         while(tempStart>=0 && tempEnd<currentState.length) {
21             if(currentState.sToChar[tempStart]!=currentState.sToChar[tempEnd])
22                 break;
23             if((tempEnd-tempStart+1) > currentState.longest) {
24                 currentState.longest = tempEnd-tempStart+1;
25                 currentState.end = tempEnd;
26                 currentState.start = tempStart;
27             }
28             tempStart--;
29             tempEnd++;
30         }
31     }
32     class StringState {
33         int start = 0;
34         int end = 0;
35         int longest = 0;
36         int length;
37         char[] sToChar;
38         public StringState(char[] sToChar) {
39             this.sToChar = sToChar;
40             this.length=sToChar.length;
41         }
42     }

  3、manacher算法(时间复杂度O(n)

  上面的朴素算法中,在判断回文字符串时,考虑了回文字符串有奇数还是偶数个字符两种情况,。然而,manacher算法首先对回文字符串做优化处理,通过在相邻字符间插入特殊字符的方式(首尾也加),将奇数长度和偶数长度回文字符串统一考虑。(这里特殊字符使用了‘#’)如:原字符串(S)为:abcba,插入特殊字符后的新串(T)为:#a#b#c#b#a#

  注:下面的代码中为防止越界,在字符串开始加一个另外的特殊字符"$"。

  (1)数组L

    manacher算法的核心思想就是,借助一个辅助数组L[T.length]记录新串T中以每个字符为中心的最长回文字符串的半径,即L[i]就是以T[i]为中心的最长回文串半径,L[i]的最小值为1。

    如,上例中新串T对应的数组L为{1,2,1,2,1,6,1,2,1,2,1}。L数组有个特性,即L[i]-1为该回文字符串(T[i]为中心,除去特殊字符)在原字符串(S)中的长度。

    改特性的证明,如下:

      首先,在字符串T中,所有的回文字符串的长度都为奇数,所以对于以T[i]为中心的最长回文字符串,其长度就为2*L[i]-1。

      其次,在字符串T的所有回文字符串中,特殊字符的数目一定比其他字符的数目多1,因此特殊字符的个数为L[i]。

      所以,可得该回文字符串在原字符串中的长度为2L[i]-1-L[i]=L[i]-1。

  (2)数组L的计算

    由上可知,只要求解出数组L便可得到字符串S的最长回文字符串。这里manacher算法使用了动态规划的思想,依次从前往后求解出数组L的值。

    假设现在求出了L[1]....L[i],记maId为之前计算的最长回文子串的右端点的最大值(如下图中黑色线的最右端),需要求L[i+k]的值(k>0)。分两种情况:

    <1>  (i+k) < maxId

      这种情况下,又分成三种情况,如下图1、2、3。当L[i]-k != L[i-k]时候(如图1、2所示),L[i+k]=min(L[i]-k,L[i-k]),图1中因为黑色的部分是回文的,且青色的部分超过了黑色的部分,所以L[i+k]肯定为L[i]-k(即橙色的部分),因为若橙色以外的部分也是也是回文的,则可以证明黑色部分再往外延伸一点也是一个回文子串,这与假设是矛盾的。图2中很好理解,利用回文的对称性,很容易得到L[i+k]=L[i-k]。总上所述可得当L[i]-k != L[i-k]时候,L[i+k]=min(L[i]-k,L[i-k])。

      当L[i]-k == L[i-k]时,如图3所示,此时,图3中橙色回文的情况完全有可能出现。所以,此时需要重新计算(归属到第二种情况)。

           浅谈manacher算法浅谈manacher算法浅谈manacher算法

             图.1.                                              图.2.                                          图.3.     

    <2>  (i+k) >= maxId

      这个时候,只能一个一个匹配了。

    下面添上java的代码:

 1     public String mancher(String s)
 2     {
 3         int i=0;
 4         char[] sConvertion = initManacher(s);
 5         int[] LArray = new int[5000];
 6         for(i=0;i<5000;i++)
 7             LArray[i] = 0;
 8         int id = 0;
 9         int mxId = 0;
10         for(i=1 ; sConvertion[i] != '$' ; i++) {
11             LArray[i] = mxId > i ? minInt(LArray[2*id - 1],mxId -i):1;
12             while(sConvertion[i+LArray[i]] == sConvertion[i-LArray[i]])
13                 LArray[i]++;
14             if((i+LArray[i]) > mxId)
15             {
16                 mxId = i+LArray[i];
17                 id=i;
18             }
19         }
20         int maxLA=0;
21         for(i=1;i<sConvertion.length;i++) {
22             if(LArray[i]>maxLA) {
23                 LArray[0] = i; //LArray[0], find max pa
24                 maxLA=LArray[i];
25             }
26         }
27         System.out.println(LArray[LArray[0]]-1);
28         return LArray[0]%2==0?s.substring((LArray[0]/2-1)-(LArray[LArray[0]]-2)/2, (LArray[0]/2-1)+(LArray[LArray[0]]-2)/2+1):
29             s.substring((LArray[0]/2-1)-(LArray[LArray[0]]-1)/2+1, (LArray[0]/2-1)+(LArray[LArray[0]]-1)/2+1);
30     }
31     public char[] initManacher(String s)
32     {
33         char sToChar[] = new char[2*(s.length())+3];
34         System.out.println(s.length());
35         int i=0;
36         sToChar[0]='@';
37         sToChar[2*(s.length())+2]='$';
38         for(i = 0;i<s.length();i++) {
39             sToChar[2*(i+1)]=s.toCharArray()[i];
40             sToChar[2*(i+1)-1]='#';
41         }
42         sToChar[2*s.length()+ 1]='#';
43         return sToChar;
44     }
45     public int minInt(int num1,int num2) {
46         return num1<num2?num1:num2;
47     }

 

posted on 2015-12-30 22:50  双飞客  阅读(1292)  评论(0)    收藏  举报

导航