1. 程式人生 > >LeetCode Longest Palindromic Substring 最長迴文子字串 兩種方法分析解答

LeetCode Longest Palindromic Substring 最長迴文子字串 兩種方法分析解答

Longest Palindromic Substring

Given a stringS, find the longest palindromic substring inS. You may assume that the maximum length ofSis 1000, and there exists one unique longest palindromic substring.

對於這道題,要怎麼設計這個table就是非常難想到的事情。因為要利用一個二維陣列,把二維陣列的兩個下標作為標示主串裡面的兩個字元位置,實在是夠難想到的了。

什麼時候使用動態規劃法:

• Optimal substructure
• Overlapping subproblems

構建解的方法:

Characterize structure of optimal solution
Recursively define value of optimal solution
Compute in a bottom-up manner

 如果是用暴力法的話,就需要O(n^3) 時間效率了,但是使用動態規劃法,就只需要O(n^2)的時間複雜度了。

最難想的地方:P代表一個表,比較難想的就是P表的下標i和j代表原字串中的兩個前後下標s[i]和s[j]的位置。
如果P[i,j]為真,當且僅當si-1,si-2...sj-1,sj這一個子串都為palindrome。例如:s[] = skrdjdre那麼P[2][6] = true,因為s[2]=r=s[6],且djd為迴文。

不明白,可以看下錶,動手填一填,未填出的都初始化為false,其中t代表填寫true:

2014-1-24 Update

Leetcode有更新了,增加了兩個大資料測試,一般使用vector容器的動態規劃法超時了,而且也禁止了使用二維陣列,會報錯memory limit exceeded。

所以更新下動態規劃法,使用3個一維陣列,測試通過,不過時間大概為836ms,還是最下面的兩邊擴充套件計算的解法最優了。

class Solution {
public:
	string longestPalindrome(string s) 
	{
		int index = 0, len = 1;
		int table[3][1000] = {1};
		int last = -1; int cur = 1;
		for (int i = 0; i < s.length(); i++)
		{
			table[0][i] = 1;
		}

		for (int i = 1; i < s.length(); i++)
		{
			if (s[i] == s[i-1])
			{
				table[cur][i] = 2;
				index = i-1;
				len = 2;
			}
			else table[cur][i] = 0;
		}

		for (int d = 2; d < s.length(); d++)
		{
			cur = (cur+1)%3; last = (last+1)%3;

			for (int i = 0, j = d; j < s.length(); i++, j++)
			{
				if (s[i] == s[j] && table[last][j-1] != 0) 
				{
					table[cur][j] = table[last][j-1]+2;

					if (table[cur][j] > len)
					{
						len = table[cur][j];
						index = i;
					}
				}
				else table[cur][j] = 0;
			}
		}
		return s.substr(index, len);
	}
};


下面就是實現上述思想的程式,時間複雜度O(n*n),空間複雜度O(n*n)

string longestPalindrome(string s) 
	{
		int n = s.length();
		vector<vector<bool> > table;
		vector<bool> temp(n, false);
		for (int i = 0; i < n; i++)
		{
			table.push_back(temp);
			table[i][i] = true;
		}
		//Attention: Don't forget we need two centor for palindrome
		int subStartPoint = 0;
		int maxLength = 1;
		for (int i = 1; i < n; i++)
		{
			if (s[i-1] == s[i])
			{
				table[i-1][i] = true;
				subStartPoint = i-1;
				maxLength = 2;
			}
		}//for
		for (int k = 3; k <= n; k++)
		{
			for (int i = 0; i <= n-k; i++)
			{
				int j=k+i-1;
				if (s[i] == s[j] && table[i+1][j-1] == true)
				{
					table[i][j] = true;
					if(maxLength < k)
					{
						subStartPoint = i;
						maxLength = k;
					}
				}
			}//for(int i = 0...
		}//for(k=3...
		return s.substr(subStartPoint, maxLength);
	}

實際leetcode執行速度:

但是其實有更加簡單的方法,實際執行速度更加快。
思想:
1. 以每個s[i]字元為中心,兩邊測試看以這個字元為中心的迴文長度是多少
2. 以每兩個字元s[i-1]s[i]為中心,測試這兩個字元是否相等,和以這兩個字元為中心的迴文有多長
最後記錄最大長度和最大長度子串起點

其實我覺得這個演算法比前面的演算法還好理解:

int testTwoSides(string &s, int low, int up)
	{
		int n = s.length();
		int max = 0;
		if (low == up)
		{
			low--;
			up++;
			max = 1;
		}
		while (low>=0 && up<n && s[low] == s[up])
		{
			max+=2;
			low--;
			up++;
		}
		return max;
	}

	string longestPalindrome(string s) 
	{
		int n = s.length();
		int subStartPoint = 0;
		int maxLength = 1;
		int temp = 0;
		for (int i = 0; i < n; i++)
		{
			temp = testTwoSides(s, i, i);
			if (temp > maxLength)
			{
				subStartPoint = i - temp/2;
				maxLength = temp;
			}
		}
		for (int i = 1; i < n; i++)
		{
			temp = testTwoSides(s, i-1, i);
			if (temp > maxLength)
			{
				subStartPoint = i - temp/2;
				maxLength = temp;
			}
		}
		return s.substr(subStartPoint, maxLength);
	}


 理論上這個演算法的時間複雜度是O(n*n),空間複雜度O(1);

記得前段時間看到某博主對於類似的這樣的演算法說成時間複雜度是O(n),所以明確說明這個時間複雜度是:O(n*n)
計算起來有點麻煩,至於是如何計算的,因為牽涉單概率論的知識和演算法時間複雜度計算基礎知識,雖然不算很難的概率論知識,但是不是那麼容易講明白的。怕講不好,而且也不用那麼麻煩每個演算法都那麼正規的分析,不然就累死人額。

所以可以對於這個演算法可以給出特定例子走一走,比如對於串aaaaaaaaaaa,那麼它就是最壞情況的時間複雜了。
實際執行效果卻是出奇的好:

2013/12/7 update:

上面的演算法其實可以利用一個迴圈就可以了,不需要多一個迴圈,不過時間效率一樣。應該可以稍微優化一點吧。

class Solution {
public:
	string longestPalindrome(string &s) 
	{
		int startPoint = 0;
		int maxLen = 1;
		for (int j = 1; j < s.length(); j++)
		{
			int t = testTwoSides(s, j, j);
			if (t > maxLen)
			{
				startPoint = j - t/2;
				maxLen = t;
			}
			t = testTwoSides(s, j-1, j);
			if (t > maxLen)
			{
				startPoint = j - t/2;
				maxLen = t;
			}
		}
		return s.substr(startPoint, maxLen);
	}
	
	int testTwoSides(string &s, int low, int up)
	{
		int n = s.length();
		int max = 0;
		if (low == up)
		{
			low--;
			up++;
			max = 1;
		}
		while (low>=0 && up<n && s[low] == s[up])
		{
			max+=2;
			low--;
			up++;
		}
		return max;
	}
};

leetCode網站上的分析的不錯了: