1. 程式人生 > >字串最長迴文子串

字串最長迴文子串

演算法介紹

給定字串str,若s同時滿足以下條件:

s是str的子串

s是迴文串

則,s是str的迴文子串。

演算法的要求,是求str中最長的那個迴文子串。

演算法詳解

第一種:列舉中心方法

迴圈索引,判斷以某索引位開始判斷前一半和後一半是否相等,如果相等計算總長度。

int enumCenter(char *S) {
	int i, j, max;
	int start = 0;
	int sSize = strlen(S);
	if (S == 0 || sSize < 1) {
		return 0;
	}
	max = 0;

	for (int i = 0; i < sSize; i++) {
        // 如果以i為中心是奇數的迴文子串
		for (int j = 0; (i - j >= 0) && (i + j < sSize); j++) {
			if (S[i - j] != S[i + j]) {
				break;
			}
			if (j * 2 + 1 > max) {
				start = i - j;
				max = j * 2 + 1;
			}
		}
        // 如果以i為中心是偶數的迴文子串
		for (int j = 0; (i - j >= 0) && (i + j + 1 < sSize); j++) {
			if (S[i - j] != S[i + j + 1]) {
				break;
			}
			if (j * 2 + 2 > max) {
				start = i - j;
				max = j * 2 + 2;
			}
		}
	}

	for (int k = start; k < start + max; k++) {
		printf("%c",S[k]);
	}
	printf("\n");

	return max;
}

第二種:Manacher演算法

因為迴文串有奇數和偶數的不同。判斷一個串是否是迴文串,往往要分開編寫,造成程式碼的拖沓。

因此我們需要想一個辦法能否將字串改造成都是奇數或者偶數呢。

一個簡單的事實:長度為n的字串,共有n-1個“鄰接”,加上首字元的前面,和末字元的後面,共n+1的“空”(gap)。因此,字串本身和gap一起,共有2n+1個,必定是奇數;

abbc → #a#b#b#c#

aba → #a#b#a#

 由於在應用中心列舉的方式,會一位一位索引確定中心位置,那我們能否應用求取的資訊獲得後面的資訊呢。

manacher演算法就是應用前面各位置求取的迴文子串長度位置資訊來推斷未求取中心的迴文子串長度。

迴文子串對應形式

首先假定我們已經將中心位置索引到了第i位置

當列舉到第個字元的時候,會出現兩種情況,第一種就是i在某個最長迴文子串中,第二種就是i不在某個最長迴文子串中

第一種:i在某個最長迴文子串中

該中情況下同樣存在兩種情況

1)i在迴文子串中,並且j的迴文半徑沒有超過ct迴文半徑

i為所求迴文子串中心,j點為通過中心對稱的點,p[i]為i點回文子串半徑,p[j]為j點回文子串半徑

 可以看到,i與j通過ct對稱,由於ct兩側的字元相同,並且p[j]還在ct半徑之內,那麼可以通過j點的迴文長度推斷出i點的迴文長度

2)i在迴文子串中,並且j的迴文半徑超過ct迴文半徑

 此時求取i的半徑,求取的是p[i]和R-i中較小的值。min(p[ct*2-i],R-i),可以推測出ct*2-i就是j的座標位置,比較的就是圖中紫色部分較小的長度。

通過上面2中情況可以看出,當p[j]沒有超過L範圍,那麼min(p[ct*2-i],R-i)取得就是p[j]的值,此時p[j]=p[i],如果超過取得就是R-i取得也是p[i]能取得值,通過這個式子將p[i]賦值,然後依次向外推尋找是否還有滿足迴文子串條件,對應第一種外推一次就不滿足條件,第二種就要看R外面是否還有滿足條件的地方了。

第二種:i不在某個最長迴文子串中

那麼由於我們沒有給定的條件支援該位置的資訊,我們只能通過該點暴力左右掃描應用列舉的方式確定迴文子串長度了。

程式碼如下:

程式碼重新組合:

// 將字串abcba拼成$#a#b#c#b#a形式
void stringChange(char *S, char *CG) {
	int sSize = strlen(S);
	CG[0] = '$';
	for (int i = 1; i < 2*sSize; i++) {
		if (i % 2 == 0) {
			CG[i] = S[i / 2];
		}
		else {
			CG[i] = '#';
		}
	}
}

manacher演算法:

int enumCenter(char *S) {
	int i, j, max;
	int start = 0;
	int sSize = strlen(S);
	if (S == 0 || sSize < 1) {
		return 0;
	}
	max = 0;

	for (int i = 0; i < sSize; i++) {
		for (int j = 0; (i - j >= 0) && (i + j < sSize); j++) {
			if (S[i - j] != S[i + j]) {
				break;
			}
			if (j * 2 + 1 > max) {
				start = i - j;
				max = j * 2 + 1;
			}
		}
		for (int j = 0; (i - j >= 0) && (i + j + 1 < sSize); j++) {
			if (S[i - j] != S[i + j + 1]) {
				break;
			}
			if (j * 2 + 2 > max) {
				start = i - j;
				max = j * 2 + 2;
			}
		}
	}

	for (int k = start; k < start + max; k++) {
		printf("%c",S[k]);
	}
	printf("\n");

	return max;
}

參考資料: