LeetCode 005. Longest Palindromic Substring

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的所有子字符串是否爲迴文字符串。長度爲n的字符串S,其子字符串的總數目爲n+(n-1)+……+3+2+1 = n*(n+1)/2

判斷子字符串是否爲迴文字符串算法複雜度O(n)就可以實現,因此總的來說此算法複雜度爲O(n^3)。

這種直觀的算法複雜度很高,在此就不採用了。這種直觀的算法思路很明瞭,就是從外向裏去檢測的,而回文字符串左右對稱,必定有一個對稱點。我們如果能將這個對稱點找出,然後再左右擴展,是不是就可以找出最長的迴文子字符串了呢?

舉個例子來說吧,S=“abcdeedcba”這個字符串是迴文字符串,當我們循環到ee處時,我們發現我們找到一個對稱點了,我們就可以左右擴展:判斷ee左右各添加一個字符,是否仍是迴文字符串,如此循環下去。

這個時候有可能會有這種字符串 S=“abcdefedcba”,這個字符串也是迴文字符串的,對稱點是 f 字符。既然有這種情況,我們就得將每一個字符都當做對稱點然後左右擴展了。

總體來說,這是有兩種情況的,我們需要分別對待,分別考慮。主要分兩種假設來分別考慮:(1)、迴文字符串長度爲偶數;(2)、迴文字符串長度爲奇數。之後再將兩者的最長挑出來。這種算法的複雜度爲O(n^2)

class Solution 
{
public:
    string longestPalindrome(string s) 
	{
		int len = s.length();
		string palindorme;
		int maxlen = 0;
		int templen = 0;
		int i,j;
		//迴文字符串長度爲偶數
		for(i=0; i<len-1; i++)
		{
			if(s[i]==s[i+1])
			{
				j = 1;
				while(i-j>=0 && i+j+1<len && s[i-j]==s[i+j+1])
					++j;
				if(2*j > maxlen)
				{
					maxlen = 2*j;
					palindorme = s.substr(i-j+1,maxlen);
				}
			}
		}

		//迴文字符串長度爲奇數
		for(i=0; i<len; i++)
		{
			j = 0;
			while(i-j>=0 && i+j<=len-1 && s[i-j]==s[i+j])
				++j;
			templen = 2*j-1;
			if(templen > maxlen)
			{
				maxlen = templen;
				palindorme = s.substr(i-j+1,maxlen);
			}
		}
		return palindorme;
    }
};
經過搜索後,發現這種算法還並非是最優算法,還有一種複雜度爲O(n)的算法——manacher算法

http://blog.csdn.net/xingyeyongheng/article/details/9310555 這篇博客介紹manacher算法邏輯比較清晰,下面就以該博客爲主體,對其做些許修改。

算法介紹如下:

定義數組 p[i] 表示以i爲中心的(包含 i 這個字符)迴文字符串半徑長度

將字符串s從前掃到後 for(int i=0; i<s.length(); i++) 來計算 p[i],則最大的p[i]就是最長迴文串長度,則問題是如何去求 p[i] ?

由於s是從前掃到後的,所以需要計算p[i]時一定已經計算好了p[1]……p[i-1]

假設現在掃描到了i+k這個位置,現在需要計算p[i+k]

定義 maxlen 是 i+k 位置前所有迴文串中能延伸到的最右端的位置,即 maxlen=p[i]+i; //p[i]+i 表示最大的。

共有三種情況需要討論:(下面圖中黑色是 i 的迴文字符串範圍,藍色是 i-k 的迴文字符串範圍)

第一種情況:i+k這個位置不在前面的任何迴文串中,即 i+k>maxlen,則初始化p[i+k]=1;//本身是迴文串

然後 p[i+k] 左右延伸,即 while(s[i+k+p[i+k]] == s[i+k-p[i+k]]) ++p[i+k]

爲了防止越界我們加上邊界判斷條件while(i+k+p[i+k]<len && i+k-p[i+k]>=0 && s[i+k+p[i+k]] == s[i+k-p[i+k]])++p[i+k];

很多博客都用字符串左右添加特殊符號*或者@的方式,這裏不採用這種方法,而是直接加上邊界條件判斷。

第一種情況圖示:


或者,如下情況



第二種情況:i+k這個位置剛好被前面以位置i爲中心的迴文串包含,即maxlen>i+k,這種情況與情況一是相同的,還是需要重新判斷 i+k 的迴文字符串長度。


第三種情況:i+k這個位置被前面以位置i爲中心的迴文串包含,即maxlen>i+k,這樣的話 p[i+k] 就不是從1開始。由於迴文串的性質可知i+k這個位置關於i與i-k對稱

i-k迴文字符串範圍有一部分在 i 的迴文字符串之外,如下圖靛色左端在黑色左端之外。


這種情況 p[i+k] = p[i]-k ; //maxlen-(i+k)=p[i]-k,即半徑爲橙色部分

那麼p[i+k]有沒有可能更長呢?答案是不可能的。證明如下:


如上圖,假設p[i+k]可以延長,增加紫色部分,我們將左右分別稱爲c和d,由i+k的迴文字符串特性可知c=d,

根據 i 的迴文字符串特性,我們可以知道與c對應的應該會有b,c=b

再根據i-k的迴文字符串特性b和紫色部分a對稱,最終得到a=b

總結上述a=b=c=d,則 i 的迴文字符串長度就不會僅僅是黑色長度了,而是黑色+左右兩端紫色部分了,這與已經求出的 i 的迴文字符串長度僅爲黑色長度矛盾,所以假設不成立,則 p[i+k]=p[i]-k; //maxlen-(i+k)


第四種情況:i+k這個位置被前面以位置i爲中心的迴文串包含,即maxlen>i+k,這樣的話 p[i+k] 就不是從1開始。由於迴文串的性質可知i+k這個位置關於i與i-k對稱

i-k 迴文字符串全部落在 i 的迴文字符串內,如下圖,此時p[i+k] = p[i-k]。



p[i+k]還有沒有可能更長呢?我們仍然採用反證法來證明



假設p[i+k]可以延長並增加紫色部分c和d,c=d

由 i 的迴文字符串對稱特性,a=d ,b=c,又c=d 故a=b

這與前面已經計算出 i-k 的迴文字符串長度僅爲靛色長度矛盾,故假設不成立。


將上面四種情況綜合在一起就是:

p[i+k] = min(p[i-k], p[i]-k); //k=(i+k)-i

while(i+k+p[i+k]<len && i+k-p[i+k]>=0 && s[i+k+p[i+k]] == s[i+k-p[i+k]])++p[i+k];


根據上面的算法我們知道p[i] 是以 i 爲中心的迴文字符串長度,那麼對於aa這種偶數長度的迴文字符串,其對稱中心 i 並不是一個字符,如何將偶數長度的迴文字符串和奇數長度的迴文字符串統一起來呢?manacher算法提供了一個非常巧妙的思路,那就是將字符串所有字符左右都插入一個 '#' 字符。如 aba 則變成 #a#b#a#,aa 則變成#a#a#,如此一來回文字符串中心均爲一個字符,而不會爲空!

下面來看下代碼:

class Solution 
{
public:
	string preprocess(string s) //將字符串中插入'#'字符
	{
		int len = s.length();
		string str(2*len+1, '#');
		for(int i=0; i<len; i++)
			str[2*i+1] = s[i];
		return str;
	}

	string longestPalindrome(string s) 
	{
		string str = preprocess(s);
		string palindrome;
		int len = str.length();
		int i,id = 0;
		int maxlen = 0; //迴文字符串最長半徑
		int index = 0;  //迴文字符串中心下標
		int * p = new int[len];	
		p[0] = 0;
		for(i=0; i<len; i++)
		{
			if(p[i]+id>i)
				p[i]=min(p[2*id-i], p[id]+id-i);
			else 
				p[i]=1;
			while(i-p[i]>=0 && i+p[i]<len && str[i-p[i]]==str[i+p[i]])
				++p[i];
			if(id+p[id] < i+p[i])
				id = i;
			if(maxlen<p[i])
			{
				maxlen = p[i];	
				index = i;
			}
		}
		delete[] p;
		palindrome = s.substr((index-maxlen+1)/2,maxlen-1);
		return palindrome;
	}
};


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章