[LeetCode] Longest Palindrome Substring 詳細分析,leetcodepalindrome
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.
面DP題的考官都是神經病。。(吐槽)
貌似挺容易出迴文的題,那麼今天仔細分析下DP的做法!!以解我被DP問題虐成渣渣的心碎感覺。如有錯誤請指出~
首先,拿了這個題,至少也可以給出一個O(n^3)的演算法,2層for迴圈加每次檢查一遍substring是不是迴文。我給出了一個必定逾時的代碼以供參考。
public String longestPalindrome(String s) { if(s==null) return null; // String[][] dp= new String[1000][1000]; // Arrays.fill(dp,1); int max=1; String result=s.substring(0,1); for(int i=0;i<s.length()-1;i++){ for(int j=i+1;j<s.length();j++){ String temp=s.substring(i,j+1); if(ifPalin(temp)){ if(max<temp.length()){ max=temp.length(); result=temp; } if(max==s.length()) return result; } } } return result; } public boolean ifPalin(String s){ if(s==null) return true; int start=0; int end=s.length()-1; while(start<end){ if(s.charAt(start++)!=s.charAt(end--)){ return false; } } return true; }
這顯然不夠!如何最佳化?你會想到這種噁心題,dp那是必然的。那麼要分析迴文,必定要考慮到所有的情況。
↑上面Naive的方法逾時原因是因為:每次迴圈,都得看一遍當前子串是不是迴文,那麼如何解決這種重複性的工作呢?
通過對迴文性質的分析,我們可以看出來: 如果中間的某一段子串sub是迴文,如果sub兩側的字元是一樣的,那麼作為一個整體也是迴文。例如:
abcdcba : d是迴文,cdc那麼就是迴文。 如果你再想一下,cdc是迴文,兩側b是一樣的,bcdcb也是迴文。咦,那麼abcdcba就是迴文了唄。
通過上個例子我們直接知道了答案。這是特例,那麼對於一般情況如何是好呢?所以第一個思路就是根據順向的思維推匯出來的:
我們申明一個dp[][]來儲存狀態。 對於dp[i][j]來說, 其意義就是 s.substring(i,j+1)是不是迴文。
(1)既然我們不知道哪裡為中心,向外拓展就能得到答案。那麼就以每個字元為中心,向外拓展,儲存一個最大的量,問題就解決了!
我們得初始化下dp, 單個字元是,所以dp[i][i]=true.
可是還有種情況, abcddcba,中間是兩個,不是一個,這也能構成迴文,這怎麼辦!
其實也好辦,我們就看看s.charAt(i)==s.charAt(i+1),如果是,就是迴文,我們就set dp[i][i+1]=true
有了基礎的case,我們1層for迴圈移動中心點,用兩個指標start,end,分別拓展這兩種情況,來分析迴文,即可找出最大值。代碼如下:
public String longestPalindrome(String s) { if(s==null) return null; boolean[][] dp= new boolean[s.length()][s.length()]; //define dp[i][j] is if s.substring(i,j+1) is palindrome: int max=1; String result=s.substring(0,1); for(int i=0;i<s.length();i++){ dp[i][i]=true; } for(int i=0;i<s.length()-1;i++){ if(s.charAt(i)==s.charAt(i+1)){ dp[i][i+1]=true; result=s.substring(i,i+2); max=2; } } for(int i=1;i<s.length()-1;i++){ int start=i-1,end=i+1; //aba的情況 while(start>=0 && end<=s.length()-1){ if(dp[i][i] && s.charAt(start)==s.charAt(end)){ dp[start][end]=true; if(max<end-start+1){ max=end-start+1; result=s.substring(start,end+1); } } else { break; } start--;end++; } start=i-1;end=i+2; //abba的情況 while(start>=0 && end<=s.length()-1){ if(dp[i][i+1] && s.charAt(start)==s.charAt(end)){ dp[start][end]=true; if(max<end-start+1){ max=end-start+1; result=s.substring(start,end+1); } } else{ break; } start--;end++; } } return result; }
上面的代碼囉嗦了點,應該能簡化,我比較懶。。如果理解意思了就好辦!
(2)上面的解法其實不太算dp,我們來考慮下真正的dp解法,但這個有時候很難想出來。。多琢磨吧。。
至少,如果你看到了這,耐心點再看一些,一定會理解dp解這個題的過程:
dp的思路是用以前做出來的結果作為基礎,然後推匯出新的結果。(2)的思路是:利用迴文的性質,如果判斷dp[i][j]是不是迴文,那麼要具備什嗎?
答案是:
1. s.charAt(i)==s.charAt(j)
2. dp[i+1][j-1] 是否為真
(這個代表當前的s.substring(i,j+1)往裡面縮一個的substring是否是迴文,有點繞。。舉例: abccbfee, 如果i=0,j=5,也就是abccbf,dp[i][j]就是說abccbf是不是迴文,根據之前的結果,如果s.charAt(i)==s.charAt(j),並且,bccb也是迴文,那麼dp[i][j]就為真. 這個例子中 a!=f, 雖然dp[i+1][j-1] (bccb)是迴文,那也不能算真)
所以這樣dp的精華就體現出來了:通過之前的結果,推導現在!
還有個關鍵點,就是dp的寫法。下午我把dp數組列印了出來,橫座標i從後往前,j是從i 到最後。1代表true,是迴文,0反之。
dp裡面挺多是從後面來的,當然我們不能記結果,而是要明白為什麼這麼做。
迴文的中心多數應該是在中心位置,所以,從後往前走,我們可以利用到之前的結果。 (從前往後走也是一樣的,只不過迴圈的參數要相應的變一下)
然而是上面的條件還不夠!如果說dp[i+1][j-1]為假,
但當i和j位置的字元是一致的,並且距離小於等於2的時候,那都是迴文,原因是只有3種情況: a (i=j) aa (i=0,j=1) a*a(i=0,j=2) *表示任一字元. 所以這樣我們可以當做迴文。
接著i不動,j接著走,超出了2的距離,也有可能會遇到和i一樣的字元,這時候是不是迴文?那就得看dp[i+1][j-1]是不是迴文,如果是,記錄,判斷是否超過當前最大長度。如果超過就替換最長字串。
看第二張圖,那三個1,其實就是例子中 abccbadd的推導過程, 當i=2的迴圈,j=2,是迴文,記錄。 i=2, j=3,“cc” 是迴文,繼續。 "ccb"不是迴文,
以上就是全部的分析,建議看文章的童鞋自己推導一遍過程。只有理解了為什麼這麼做,才能真正理解dp,從而寫出出精鍊乾淨的dp代碼
:下面代碼是從大神那看到的,拿過來的。
public String longestPalindrome(String s) { if(s==null || s.length()==1) return s; boolean[][] dp= new boolean[s.length()][s.length()]; //define dp[i][j] is if s.substring(i,j+1) is palindrome: int max=0; String result=s.substring(0,1); for(int i=s.length()-1;i>=0;i--){ for(int j=i;j<s.length();j++){ if(s.charAt(i)==s.charAt(j) && ((j-i<=2) || dp[i+1][j-1])){ dp[i][j]=true; if(max<j-i+1){ max=j-i+1; result=s.substring(i,j+1); } } } } return result; }
ACM問題 這個題的輸入問題怎解決? http://acmswjtueducn/JudgeOnline/showproblem?problem_id=139
先把所有輸入都讀完,再去做進一步處理。
讀取代碼:
char *pBuf;
memset(buf, 0, sizeof(buf); // buf是你前面定義的數組
pBuf = buf;
while(gets(pBuf)) pBuf += strlen(pBuf);
這樣,所有字元都被讀進了buf,並且所有分行符號都被去除了。接下來你只要實現演算法部分就可以了。