leetcode 5. 最長回文子列暴力法、中心拡張アルゴリズム、動的計画、マラ車アルゴリズム(Manacher Algorithm)


        s,   s         。      s        1000。

   1:
  : "babad"
  : "bab"
  : "aba"         

まず、テーマを理解することが重要です!!!返事は何ですか.
          [1]  ,             ,         ,    ,    。

この問題は時間の複雑さには要求していないが、それでも最初は仕方がない.しばらく考えて、遍歴して、俗に暴力法と呼ばれて、結局問題を解決するのが最も重要で、最適化は引き続きすることができます.簡単な解析から,回文の文字列は2つの対応が等しいことが分かる.長さnの返信文字列を仮定し、以下の条件を満たす
s[i] == s[n-i-1]

与えられた文字列の各文字を巡り、その文字列で始まる最長の回文文字列を見つけます.与えられた開始文字について、最も長いことを考慮すると、文字列の末尾から後へ遍歴するに違いありません.firstを使用して、lastの2つのポインタはそれぞれ先頭を指し、first>lastまで遍歴すると遍歴が終了し、得られた長さと開始位置が保持されます.次の文字ループを開始します.
遍歴の過程で,上述した暴力法が最適化できることが分かったが,例を挙げると,s[i]の最長回文サブシリアルテールポインタはs[j]であり,iの次の要素s[i+1]を遍歴すると,s[i+1]とs[j-1]が回文になるまで前のステップで行けるので,j+1~lastの区間を遍歴するだけでよい.同様に、後続の各要素については、末尾要素から以前の最近の返信文字まで遍歴するだけでよい.各先頭文字first[i]に対して、その対応する最長回文サブシリアル文字がlast[i]であると仮定し、先頭文字first[j](ichar * longestPalindrome(char * s){ int first, last, len = 0, i, j, max = 0, cursor, jump = 0; char *ps = s; if (!s) return s; while(*ps++ != '\0')len++; // for (i = 0; i < len; i++) { for (j = len - 1; j >= jump; j--) { first = i, last = j; while (first <= last){ if (s[first++] != s[last--]) break; } if (s[first-1] == s[last+1]){ if (j - i + 1 > max) { max = j - i + 1; cursor = i; } //jump ,i jump // 。 jump = jump > last + 1 ? jump: (last + 1); break; } } } ps = s + cursor; if (max < len) ps[max] = '\0'; return ps; }
暴力遍歴法は時間的複雑度がO(n^3),中心拡張法は時間的複雑度がO(n^2)であり,この方法は実はかなり簡単である.暴力遍歴文列方向が両端から中間にマッチしているとすれば、中心拡張方向は中心から両端に及ぶ.ここで中心は奇数中心Nと偶数中心N−1を含む2 N−1個である.中心拡張メソッドは、中心から両端に遍歴し、不一致に遭遇すると終了します.
#define MAX(a,b) ((a)>(b)?(a):(b))
int centerExpand(char *s, int len, int left, int right)
{
	while(left >= 0 && right < len && s[left] == s[right])
	{
		left--;
		right++;
	}

	return right - left - 1;
}

char * longestPalindrome(char * s){
	int len = 0, center = 0, max = 0, max0 = 0, max1 = 0, cursor = 0;
	char *pStr = s;

	if (!s)return s;
	while (*pStr++ != '\0')len++;

	for (center = 0; center < len; center++)
	{
		max0 = centerExpand (s, len, center, center);
		max1 = centerExpand (s, len, center, center+1);
		
		if (max < max0 || max < max1)
		{
			if (max0 > max1)
                cursor = center - max0 / 2;
            else
                cursor = center + 1 - max1 /2;

			max = MAX(max0, max1);
		}
	}

	pStr = s + cursor;
	if (max != len)
		pStr[max] = '\0';
		
	return 	pStr;
}

3つ目はダイナミックプランニングで、これは私が覚えてから書きます.
=========================================================================================
動的計画の基本手順は次のとおりです.
1.段階別
2.状態繰返し方程式
3.最適解を求める.
この問題はダイナミックプランニングを使用する場合、iとjがそれぞれ文字の位置を表し、m[i][j]が文字iからjが回文文字列であるかどうかを表すm[i][j]タイプを使用することは明らかである.もしそうならm[i][j]=1;そうでなければm[i][j]=0;繰返し方程式はm[i][j]=m[i+1][j-1]&&(s[i]==s[j]);長さ1と2のm[][]値を先に算出することができます.
m[i][i] = 1; m[i][i+1] = (s[i] == s[i+1]);次に、最も長い返信文字列値max=j-i+1を記録することができる.起点はi.
難易度は大きくないが,通常動的計画を用いる場合m[i][j]が最適解を表すために用いられ,本題は状態のみが保存されているとは容易に考えられない.もっと練習しなければなりません.
次に、動的計画のcコードを示します.

char * longestPalindrome(char * s){
	int i,j,d, len = 0, m[1001][1001]={0}, max = 1, c = 0;
	char *pStr = s;

	if (!s)return s;
	while (*pStr++ != '\0')len++;

    if (len <= 1)
        return s;
    
    //     ,           
    for (i = 0; i < len - 1; i++)
    {
        m[i][i] = 1;
        if (s[i] == s[i+1])
        {
            m[i][i+1] = 1;
            if (max < 2)
            {
                max = 2;
                c = i;
            }            
        }   
        else
            m[i][i+1] = 0;
            
    }    
    m[i][i] = 1;
    
    //  ,  3   n-1    
    for (d = 2; d < len; d++)
    for (i = 0; i + d < len; i++)
    {
        j = d + i;
        m[i][j] = (m[i+1][j-1]) && (s[i] == s[j]);
        if (m[i][j] && (j-i+1) > max)
        {
            max = j - i + 1; c = i;
        }
        
    }

    s = s + c;
    s[max] = '\0';
        
	return 	s;
}


=============================================================================================
Linuxアプリケーション、カーネル、ドライバ、バックグラウンド開発交流討論群(745510310)、興味のある学生はグループ討論、交流、資料検索などを加えることができて、前進する道の上で、あなたは一人ではありません^^.