一、概述
輸入一個字符串,輸出它的最長迴文子串。
經典DP問題。爲什麼突然想要做DP了呢?因爲最近這幾次打Contest,每次第三題和第四題必定是DP,不用DP做不出來那種。所以就自閉了。有的是自己寫了半天一直TLE才後知後覺需要DP。因此痛定思痛,要開始學一下DP。拿之前沒做過的DP題目來練手。因爲是新學習的,所以就先不糾結時空複雜度了,能做得出來就好。
二、分析
最愚蠢的方法就是先看第一個,然後從第一個開始看到最後一個,找出最長的;然後從第二個開始看到最後,需要O(n^3)的時間。使用DP則僅需要O(n^2)的時間。那麼如何使用DP呢?
DP在思考上的核心是找到遞推公式,也就是我現在已經知道一個樣例是對的了,那麼在這個樣例上加上什麼條件可以得到下一個樣例呢?以此題爲例,假設我們已經知道字符串的s[3,5]是一個迴文串,那麼加上什麼條件可以得到下一個迴文串?看字符串的2和6,如果s[2]==s[6],那麼就找到了下一個迴文串。
也就是說,想知道s[i,j]是不是迴文串,我們需要知道s[i+1,j-1]是不是,同時要判斷s[i]和s[j]是否相等。
遞歸公式需要給出前兩項,因此我們需要先手動算出長度爲1和長度爲2的所有子串是否爲迴文串,然後其餘長度的在後面遞推就可以得到了。如下圖所示:
DP在實現上的核心是如何通過s[i,j]找到s[i+1,j-1]。也就是如何實現遞推關係。本題所使用的是二維DP表,以babad爲例,寫下來DP表如下:
從上圖中我們可以很輕鬆的得到遞推關係的實現:
dp[i][j]=(dp[i-2][j+1])&&(s[j]==s[j+i]);
注意這裏,i+1是子串長度,j是子串最左端元素的下標。
從而整體代碼如下:
class Solution {
public:
string longestPalindrome(string s) {
int dp[1010][1010]={0};
if(s.size()==0)
{
return "";
}
int max_i,max_j;
for(int i=0;i<s.size();++i)
{
int flag=0;
for(int j=0;j<s.size()-i;++j)
{
if(i<2)
{
dp[i][j]=(s[j]==s[j+i]);
if(dp[i][j]==1&&flag==0)
{
max_i=i;
max_j=j;
flag=1;
}
}
else
{
dp[i][j]=(dp[i-2][j+1])&&(s[j]==s[j+i]);
if(dp[i][j]==1&&flag==0)
{
max_i=i;
max_j=j;
flag=1;
}
}
}
}
return s.substr(max_j,max_i+1);
}
};
三、總結
最長迴文子串還有更好的Manacher算法,我的算法也還有很大的優化空間。但是這是最簡單最直觀的一種實現了。優化的的問題,等到我對DP熟稔之後再說也不遲。DP給我的感覺,emmmm,類似數學歸納法?先是寫好最基本的情況,然後一層一層遞歸,得到一般的情況。有一說一,當時高中我數學歸納法就不怎麼樣。