天天看點

字元串—馬拉車算法(求最長回文串)字元串—馬拉車算法(求最長回文串)

字元串—馬拉車算法(求最長回文串)

作為一個字元串渣渣,對于我來說,看見此類問題總會忍不住虎軀一震。。。初次見到“求最長回文串”問題時,我腦子裡無非蹦出兩個想法:

1.用棧的結構來存儲,然後出棧看是否滿足回文串的條件(回文串定義相信大家都知道,在此就不啰嗦了);

2.看見最長,立馬想到用動态規劃的思想,寫出狀态轉移方程,找出最優子結構。

其實這兩個思路合并起來是否可以解決問題——答案是肯定的。但這樣一來的時間複雜度和空間複雜度都是很大的(時間複雜度

文章目錄

  • 字元串—馬拉車算法(求最長回文串)
    • 引入—回想KMP算法
    • 進入正題—馬拉車算法的思路
      • 半徑數組
      • 半徑數組的計算
    • 代碼實作
    • 總結

引入—回想KMP算法

KMP算法作為《資料結構》——串一章中,最重要的一個算法,想必大部分人都不陌生。同樣的,這也是字元串問題中一個經典的将時間複雜度大大優化的經典算法。

我們先來回顧一下,KMP算法的核心是什麼——找出待比對的子串的最長公共字首,一次确立next數組,作為對位失敗後下次移位的基準。

是以說,欲解決目前字元串與其他字元串的問題,須先從自身下手——這也正是馬拉車算法的方法論。

進入正題—馬拉車算法的思路

半徑數組

首先我們都知道,回文串有兩種情況:

(1)串長為基數:如abcba,該串以c為中心點作回文分界;

(2)串長為偶數:如abccba,中心點為兩個字元的分界。

針對這兩種情況,為實作統一,先在串中每個字元間(首尾端都要插入)插入一個不沖突的特殊符号作為,例如:

原串為abbba,插入一個特殊符号#後變為a#b#b#b#a#

這樣做的目的是:插入後字元串長度肯定為奇數了(奇+偶=奇),這樣做之後就肯定不用考慮串的奇偶性了。

接下來我們與KMP的next數組一樣,引入一個概念:半徑數組

這個數組p[i]用于記錄以s[i]為中心的最長回文串的半徑(不包括p[i]本身)。當p[i]=0時,說明回文串是字元串本身。

比如,#a#b#的半徑數組為{0,1,0,1,0}

仔細體會一下,最大半徑是不是就是最大回文串的半徑?

這時我們還需注意的一點是:回文串搜尋時我們需避免越界的問題。是以還要在已經處理過一遍的字元串外頭尾加上兩個特殊符号,例如:

#a#b#變為$#a#b#*

至于為什麼計算的是半徑,在于為了友善計算邊界。

半徑數組的計算

傳統的 O ( n 2 ) O(n^2) O(n2)的做法,是以中心店開始,挨個将半徑擴張,這麼一來時間複雜度就很高,因為既要周遊、又要比對;

而馬拉車算法巧妙的借用了p[i]數組,一直不斷更新兩個變量:

(1)id:回文串的中心位置;

(2)mx:回文串的最後位置;

p[i]=min(mx-i,p[2 id-i])

想想,為什麼p[i]會如此定義?

(1)當周遊時一直都符合回文,mx-i為最壞情況下p[i]的最大值;而2id-i則為,當mx-i>p[i]時,由于對稱性,2id-i和i肯定是對稱的,是以以s[i]為中心的回文子串必包含在以s[id]為中心的回文子串中。

代碼實作

此處指給出核心代碼。并沒有使用p數組表示,但思路一緻

const maxn=10005;
const int mx=0,id=0;
int min(int &x,int &y)
{
	if (x<y) return x;
	else return y;
}
int max(int &x,&y){
	if(x>y) return x;
	else return y;
}
int Manacher(char str[],int mx,int id)
{
	int len[maxn];
	len[0]=0;
	for(int i=1;i<len;i++){
		if(i<mx) Len[i]=min(mx-i,len[2*id-i]);
		else len[i]=1;//回文串本身
		while(str[i-len[i]]==str[i+len[i]]) len[i]++;
		if(len[i]+i>mx){//越界,則一直周遊
			mx=len[i]+i;
			id=i;
			sum=max(sum,len[i]);
		}
	}
	return (sum-1;
}
           

總結

馬拉車算法與KMP算法類似,需要先對字元串内部自身進行比較,且設定一個輔助數組。雖然說相比傳統的動态規劃方法,節省了時間開銷,但在半徑數組的求值問題中也用到了dp思想。當然,半徑數組的設定也不是必要的。還有一點須注意的是,由于回文串存在奇偶性,應先對字元串進行預處理。

繼續閱讀