1. 程式人生 > >Manacher演算法:求解最長迴文字串,時間複雜度為O(N)

Manacher演算法:求解最長迴文字串,時間複雜度為O(N)

迴文串定義:“迴文串”是一個正讀和反讀都一樣的字串,比如“level”或者“noon”等等就是迴文串。迴文子串,顧名思義,即字串中滿足迴文性質的子串。

經常有一些題目圍繞回文子串進行討論,比如POJ3974最長迴文,求最長迴文子串的長度。樸素演算法是依次以每一個字元為中心向兩側進行擴充套件,顯然這個複雜度是O(N^2)的,關於字串的題目常用的演算法有KMP、字尾陣列、AC 自動機,這道題目利用擴充套件KMP可以解答,其時間複雜度也很快O(N*logN)。但是,今天筆者介紹一個專門針對迴文子串的演算法,其時間複雜度為O(n),這就是manacher 演算法。

大家都知道,求迴文串時需要判斷其奇偶性,也就是求aba 和abba 的演算法略有差距。然而,這個演算法做了一個簡單的處理,很巧妙地把奇數長度迴文串與偶數長度迴文串統一考慮,也就是在每個相鄰的字元之間插入一個分隔符,串的首尾也要加,當然這個分隔符不能再原串中出現,一般可以用‘#’或者‘$’等字元。例如:
原串:abaab
新串:#a#b#a#a#b#
這樣一來,原來的奇數長度迴文串還是奇數長度,偶數長度的也變成以‘#’為中心奇數迴文串了。
接下來就是演算法的中心思想,用一個輔助陣列P 記錄以每個字元為中心的最長迴文半徑,也就是P[i]記錄以Str[i]字元為中心的最長迴文串半徑。P[i]最小為1,此時迴文串為Str[i]本身。
我們可以對上述例子寫出其P 陣列,如下
新串: # a # b # a # a # b #
P[] : 1 2 1 4 1 2 5 2 1 2 1
我們可以證明P[i]-1 就是以Str[i]為中心的迴文串在原串當中的長度。
證明:
1、顯然L=2*P[i]-1 即為新串中以Str[i]為中心最長迴文串長度。

2、以Str[i]為中心的迴文串一定是以#開頭和結尾的,例如“#b#b#”或“#b#a#b#”所以L 減去最前或者最後的‘#’字元就是原串中長度      的二倍,即原串長度為(L-1)/2,化簡的P[i]-1。得證。 依次從前往後求得P 陣列就可以了,這裡用到了DP(動態規劃)的思想,       也就是求P[i] 的時候,前面的P[]值已經得到了,我們利用迴文串的特殊性質可以進行一個大大的優化。

先把核心程式碼貼上:

for (i = 0; i < len; i++){
         if (maxid > i){
             p[i] = min(p[2*id - i], maxid - i);
         }
         else{
              p[i] = 1;
         }
         while (newstr[i+p[i]] == newstr[i-p[i]])
                p[i]++;
         if (p[i] + i > maxid){
             maxid = p[i] + i;
             id = i;
         }
         if (ans < p[i])
             ans = p[i];
     }

為了防止求P[i]向兩邊擴充套件時可能陣列越界,我們需要在陣列最前面和最後面加一個特殊字元,令P[0]=‘$’最後位置預設為‘\0’不需要特殊處理。此外,我們用MaxId 變數記錄在求i 之前的迴文串中,延伸至最右端的位置,同時用id 記錄取這個MaxId 的id 值。通過下面這句話,演算法避免了很多沒必要的重複匹配。
if (maxid > i){
             p[i] = min(p[2*id - i], maxid - i);
         }

那麼這句話是怎麼得來的呢,其實就是利用了迴文串的對稱性,如下圖,

j=2*id-1 即為i 關於id 的對稱點,根據對稱性,P[j]的迴文串也是可以對稱到i 這邊的,但是如果P[j]的迴文串對稱過來以後超過MaxId 的話,超出部分就不能對稱過來了,如下圖,


所以這裡P[i]為的下限為兩者中的較小者,p[i]=Min(p[2*id-i],MaxId-i)。演算法的有效比較次數為MaxId 次,所以說這個演算法的時間複雜度為O(n)。

下面就貼一個具體程式碼,求解最長迴文字串的程式碼:

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int MAX = 100001;
int len, p[2*MAX];
char str[2*MAX], newstr[2*MAX];

void change()
{
     int i;
     newstr[0] = '@';
     newstr[1] = '#';
     for (i = 0; i < len; i++){
         newstr[2*i + 2] = str[i];
         newstr[2*i + 3] = '#';
     }
     newstr[2*len + 2] = '\0';
     return ;
}


void Manacher()
{
     int i, j, id, maxid = 0, ans = 1;
     len = 2 * len + 2;
     for (i = 0; i < len; i++){
         if (maxid > i){
             p[i] = min(p[2*id - i], maxid - i);
         }
         else{
              p[i] = 1;
         }
         while (newstr[i+p[i]] == newstr[i-p[i]])
                p[i]++;
         if (p[i] + i > maxid){
             maxid = p[i] + i;
             id = i;
         }
         if (ans < p[i])
             ans = p[i];
     }
     
     for (i = id, j = 0; i < id + ans; i++){
          if (newstr[i] != '#'){
              str[j] = newstr[i];
              j++;
          }
     }
     str[id+ans] = '\0';
     cout << ans - 1 << " " << str << endl;
     return ;
}


int main()
{
    while (scanf("%s", &str)){
          if (strcmp(str, "END") == 0)   break;
          len = strlen(str);
          change();
          Manacher();
    }
    
    system("pause");
}


相關推薦

Manacher演算法求解字串時間複雜O(N)

迴文串定義:“迴文串”是一個正讀和反讀都一樣的字串,比如“level”或者“noon”等等就是迴文串。迴文子串,顧名思義,即字串中滿足迴文性質的子串。 經常有一些題目圍繞回文子串進行討論,比如POJ3974最長迴文,求最長迴文子串的長度。樸素演算法是依次以每一個字元為中心

Manacher演算法時間複雜O(n)

最長迴文子串 問題 對於一個字串,請設計一個高效演算法,計算其中最長迴文子串的長度。 給定字串A以及它的長度n,請返回最長迴文子串的長度。 測試樣例: “abc1234321ab”,12 返回:7 中心擴充套件到Manache

【2019新浪&微博筆試題目】判斷連結串列是否結構空間負責O(1)時間複雜O(n)

原題描述 判斷一個連結串列是否為迴文結構,要求額外空間複雜度為O(1),時間複雜度為O(n) 解題思路 一、雙向連結串列 如果連結串列是雙向連結串列,那簡直不要太完美。直接從連結串列兩端向中間遍歷即可判定 可惜,這個題目肯定不會說的是這種情況,

遞增子序列時間複雜O(nlogn))

package com.kailong.datastures; import java.util.Arrays; /** * Created by Administrator on 2017/4/17. * 最長遞增子序列 */ public class Find

實現排序演算法時間複雜O(n)

我們常用的排序氣泡排序 O(n^2); 快速排序O(nlogn);堆排序O(nlogn);選擇排序O(n^2); 我們常用的排序都不符合時間複雜度的要求; 經常聽說一個說法  用空間代替時間 現在要排序的陣列為陣列 a;例如a數組裡面有  1,1,2,2,3,3,2,2,5

大連續子序列的和時間複雜 O(n)

練習題目 給定陣列 [ a0, a1, a2, …, an ] ,找出其最大連續子序列和,要求時間複雜度為 O(n),陣列包含負數。 例如:輸入 [ -2,11,-4,13,-5,-2] ,輸出 20(即 11 到 13)。 解答 關於這個問題有很多種解法,這裡介紹一種時間複雜度僅為 O(n)

第4章 貪心演算法Dijkstra演算法(鄰接矩陣儲存時間複雜O(n^2))

#include <iostream> #include <cstdio> #include <cstring> using namespace std; #d

尋找陣列中第k小的數平均情況下時間複雜O(n)的快速選擇演算法

又叫線性選擇演算法,這是一種平均情況下時間複雜度為O(n)的快速選擇演算法,用到的是快速排序中的第一步,將第一個數作為中樞,使大於它的所有數放到它右邊,小於它的所有數放到它左邊。之後比較該中樞的最後位

演算法設計將一個數組分奇數、偶數左右兩個部分要求時間複雜O(n)

        已知陣列A[n]中的元素為整型,設計演算法將其調整為左右兩部分,左邊所有元素為奇數,右邊所有元素為偶數,並要求演算法的時間複雜度為O(n)。 程式碼實現部分: #include &l

棧表中獲取小值時間複雜O(1)

       近期複習資料結構,看到網上有一道演算法題,該題目曾經是google的一道面試題,國內的網際網路公司也紛紛效仿。我也順便複習之。        題目內容為:對現在的stack(棧)資料結構進行改進,加一個

在一個含有空格字元的字串中加入XXX,演算法時間複雜O(N)

import java.util.Scanner; /** * */ /** * @author jueying: * @version 建立時間:2018-10-18 下午10:54:54 * 類說明 */ /** * @author jueying

實現一個出棧入棧返回小值的操作的時間複雜O(1)的棧

棧的特點的是先進後出,這裡實現的棧是在這個基礎上加以特定的功能。 用一個原生棧實現 這種方法需要_min來記錄棧頂元素到棧底元素的最小值,每次入棧之前需要先比較入棧元素和_min的值,接著將它兩依次入棧;出棧的時候需要每次Pop兩次,並記錄第二次的值,即為新

時間複雜O(N*logN)的常用排序演算法總結與Java實現

時間複雜度為O(N*logN)的常用排序演算法主要有四個——快速排序、歸併排序、堆排序、希爾排序1.快速排序·基本思想    隨機的在待排序陣列arr中選取一個元素作為標記記為arr[index](有時也直接選擇起始位置),然後在arr中從後至前以下標j尋找比arr[inde

長度n的順序表L編寫一個時間複雜O(n),空間複雜O(1)的演算法演算法刪除線性表中所有值X的元素

解法:用K記錄順序表L中不等於X的元素個數,邊掃描L邊統計K,並將不等於X的元素向前放置K位置上,最後修改L長度 void  del_x_1(SqList &L,Elemtype x){ int k=0; for(i=0;i<L.length;i++) {

有1,2,....一直到n的無序陣列,求排序演算法要求時間複雜O(n),空間複雜O(1)

http://blog.csdn.net/dazhong159/article/details/7921527 1、有1,2,....一直到n的無序陣列,求排序演算法,並且要求時間複雜度為O(n),空間複雜度O(1),使用交換,而且一次只能交換兩個數。 #include &

Manacher馬拉車演算法求解子串

        馬拉車演算法是一種能在O(n)的時間複雜度範圍內得出結果。我看了不下幾次這個演算法,每次都覺得有點懂了,但是一碰到題目了,就生生寫不出來。歸根揭底,還是沒有掌握其思想。 馬拉車演算法的第一個核心思想就是往原始的字串裡填充一些輔助的東西,使得我們在考慮問題時不

Girls' research(已完善的Manacher演算法模板輸出子串)

One day, sailormoon girls are so delighted that they intend to research about palindromic strings. Operation contains two steps: First step: girls will wr

資料結構與演算法隨筆之------子串四種方法求解(暴力列舉+動態規劃+中心擴充套件+manacher演算法(馬拉車))

所謂迴文串,就是正著讀和倒著讀結果都一樣的迴文字串。 比如: a, aba, abccba都是迴文串, ab, abb, abca都不是迴文串。 一、暴力法 方法一:直接暴力列舉 求每一個子串時間複雜度O(N^2), 判斷子串是不是迴文O(N),兩者是相乘關係,所以時間

字串演算法-Manacher’s Algorithm-馬拉車演算法

除了翻譯之外,其中還加入了個人的理解的部分,將其中沒有詳細說明的部分進行了解釋。 時間複雜度為O(n)的演算法 首先,我們需要講輸入的字串 S 進行一下轉換得到 T,轉換的方法就是通過在每兩個字元之間插入一個字串“#”,你馬上就能知道為什麼要這麼做。

字串--MANACHER演算法

個人感覺馬拉車演算法的思想和擴充套件KMP的思想是相似的。 首先對於這個問題,我們可以暴力列舉每個子串,然後判斷是否是迴文串,時間複雜度大概是O(n^3),我們運用下尺取法的思想,列舉每一個對稱軸位置(針對長度的奇偶有所區別),那麼時間複雜度會是O(n^2),接著我們如果把