leetcode 第四題:動態規劃思想的應用
題目大意:給定一個字串,找出其最長的子迴文串將其返回。所謂迴文串,即倒序排列與正序排列一致。
說明:假定字串長度不超過1000
示例:輸入"babad",輸出"bab"或者"aba"
解題思路:關於題目有幾點要注意的。一是題目預設是有解的,當輸入如"abcde"的形式時,解當為"a"或者任一字元代表的字串;二是有多個解,只需輸出一個即可。
在思考這道題時,最先冒出的想法很簡單,即以第一個字元為頭開始,逐步增加字串長度,判斷是否為迴文串。若不是,記錄最大長,並跳到下一字元,以其開頭。這樣做雖然簡單,但時間複雜度過高,很容易出現超時。
聯想到之前做的一道題,我試著用“滑動窗”法去解決它。如下:
思路也不復雜。從左往右逐個遍歷字串中的元素,判斷該元素與之前的元素是否形成對稱。若形成對稱,則保留對稱資訊,繼續遍歷下一元素。此方法在本地測試時沒有出現問題,但上傳後結果是WA。我思考了一下,發現在編寫程式時,漏掉了一種些情況。比如輸入"aaaaa",結果輸出"aaaa",原因在於遍歷到第3個‘a’時,程式只檢查了其與之前元素對稱的情況,而跳過了第三個'a'中心對稱的情形。另外,對於結果是單個字元的情況,也沒有做處理。class Solution { public: string longestPalindrome(string s) { int j,k1,k2,left=0,right=0,res=0; //記錄滑動窗的左右邊界 string ans; k1=1; k2=1; for(j=2;j<s.length();j++) //遍歷字串 { if(j>=2*k1&&s.at(j)==s.at(j-2*k1)) //如果奇對稱的話 { if(2*k1+1>res) { left=j-2*k1; //更新左右的值 right=j; res=2*k1+1; } k1++; } else k1=1; if(j>=2*k2-1&&s.at(j)==s.at(j+1-2*k2)) //如果偶對稱的話 { if(2*k2>res) { left=j+1-2*k2; right=j; res=2*k2; } k2++; } else k2=1; } ans=s.substr(left,res); return ans; } };
在做了一定改動結果仍不正確後,我參考了solution中的答案。其中比較有代表性的就是利用動態規劃的思想去解決。
在這種方法中,首先需要給出動態規劃的遞推式。本題中的遞推式如下:
其中,
簡單地說,就是用P(i,j)表示對於從下標i到j的子字串是否為迴文串,如果其是迴文串,則為邏輯真,否則為邏輯假。那麼對於一個迴文串,如果其一前一後兩個字元又相同,則可將其“擴充”成更長的迴文串。基於此,只要找出給定字串中長為1或者2的子迴文串,然後對其逐個擴充至最長,記錄下最長迴文串的資訊,返回即可。實現程式碼如下:
class Solution {
public:
string longestPalindrome(string s) {
int i,j,k,t=0,palindrome[2000][2],res=0,ans=1;
string ret;
memset(palindrome,0,sizeof(palindrome));
for(i=0;i<s.length()-1;i++) //最後一個字元單獨考慮
{
if(s.at(i)==s.at(i+1))
{
palindrome[i][0]=i;
palindrome[i][1]=i+1;
palindrome[s.length()+t][0]=i;
palindrome[s.length()+t][1]=i;
t++;
}
else
{
palindrome[i][0]=i;
palindrome[i][1]=i;
}
}
palindrome[s.length()-1][0]=s.length()-1;
palindrome[s.length()-1][1]=s.length()-1;
for(j=0;j<s.length()+t;j++) //動態規劃以擴充迴文串
{
k=0;
while(palindrome[j][1]+k<=s.length()-1&&palindrome[j][0]>=k&&s.at(palindrome[j][0]-k)==s.at(palindrome[j][1]+k))
k++;
if(res<k*2+(palindrome[j][0]==palindrome[j][1]?-1:0))
{
res=k*2+(palindrome[j][0]==palindrome[j][1]?-1:0);
ans=j;
palindrome[j][0]=palindrome[j][0]-k+1;
palindrome[j][1]=palindrome[j][1]+k-1;
}
}
ret=s.substr(palindrome[ans][0],res);
return ret;
}
};
值得注意的是,在編寫程式碼時比較容易遺漏一些情形。比如輸入連續重複字元時,若沒有變數t,就會出現比正確結果少1的情況。
本方法的時間複雜度時O(n^2),在接受範圍內。在solution中還提供了一種複雜度僅為O(n)的方法,不過由於不具有一般性,此處不贅述。
動態規劃屬於比較常見的處理手段,其難點在於想到以動態規劃的方法去解題以及建立動態規劃的關係式。希望能從這道題中引以為戒,學會更多資料處理方法。