1. 程式人生 > >五大經典演算法之三動態遞迴DP

五大經典演算法之三動態遞迴DP

五大經典演算法 動態遞迴DP

首先需要決定儲存什麼歷史資訊,以及用什麼資料結構來儲存。然後最重要的就是遞推公式,最後需要考慮起始條件的值。

Leetcode 139. Word Break

要求一個非空字串s,一個非空的字串詞典,判斷s能夠通過空格組成一個序列是詞典裡的多個單詞:

例如s="leetcode"

dict=["leet","code"

]

因為“leetcode”可以改成“leet code”故返回1

使用DP,我們用dp[i]表示到字串s的第i個元素為止能不能用字典裡的詞表示。假設已經知道dp[0,1,,,,i-1]的結果,要求dp[i],其遞推關係:

len取自[minlen,maxlen],對於i從0到s.length,都有:

dp[i+len]=1 when dp[i]=1&&wordDict.fin(s.substr(i,len))

程式碼:

//word break
bool find_vector(vector<string> ss,string s){
int i=0;
while(i<ss.size()){if(s.compare(ss[i])==0)return 1;i++;}
return 0;
}
bool wordBreak(string s,vector<string>& wordDict){
if(s.size()<1)return 0;
vector<bool> dp(s.length()+1,false);
dp[0]=true;
int min_len=INT_MAX,max_len=0;
for(int i=0;i<wordDict.size();i++){if(min_len>wordDict[i].size()){min_len=wordDict[i].size();}
                                   if(max_len<wordDict[i].size()){cout<<1;max_len=wordDict[i].size();}
                                  }
for(int i=0;i<s.size();i++){
if(dp[i]){for(int len=min_len;i+len<=s.size()&&len<=max_len;len++)
             if(find_vector(wordDict,s.substr(i,len)))dp[i+len]=1;
          }
if(dp[s.length()])return 1;
                            }
return dp[s.length()];
                                                  }

132. Palindrome Partitioning II
要求給定一個字串,要求分成的若干字串都是迴文,求最小分串次數。

本題採用動態規劃法從後往前,引出dp陣列,dp[i]表示s[0...i]的最小分割次數;

dp[i]初始化為i,每一個之間切一刀,這是最大值了;

若從0到i之間存在j,0<j<i,且有s[j...i]是迴文,那麼此時dp[i+1]=min(dp[i1+],dp[j]+1)

故遞推公式:

dp[i+1]=min(dp[i1+],dp[j]+1)

vector <vector <bool> > getdict(string s){
vector< vector<bool> > res;
for(int i=0;i<s.length();i++){
    vector<bool> aa;
    for(int j=0;j<s.length();j++){aa.push_back(0);};
    res.push_back(aa);
                              }
if(s.size()<1)return res;
for(int i=s.length()-1;i>=0;i--)
    for(int j=i;j<s.length();j++)
	if(s[i]==s[j]&&((j-i<2)||res[i+1][j-1]))res[i][j]=1;

return res;
                                          } //判斷任意字串之間是不是迴文
//palindrome Partitioning11
int minCut(string s){
int len=0;
if(s.length()<1)return len;
vector< vector<bool> >dict=getdict(s);
vector<int> dp(s.length()+1,0);
dp[0]=0;
for(int i=0;i<s.size();i++){
 {
     dp[i+1]=i+1;//初始化
     for(int j=0;j<=i;j++)
	 if(dict[j][i])
	     dp[i+1]=min(dp[i+1],dp[j]+1);
 }
                               }
return dp[s.size()]-1;
}


已知‘?’可以匹配任何一個字元

已知‘×’可以匹配0個或者多個任意字元

求兩個字串是否完全匹配。

類似上面的,這裡引入動態規劃,設dp[i][j]表示s的前i個字元與p的前j個字元的匹配情況,其遞推公式:

dp[i][j]=dp[i][j-1]||dp[i-1,j] when p[j]=='*',當此星號表示0個字元時,則主要看dp[i][j-1],當星號代表字元時,則結果主要在於前面dp[i-1][j]

dp[i][j]=dp[i-1][j-1]&&(s[i]==p[j]||p[j]=='?') when p[j]!='*'

注意s的前i個字元與p的前j個字元的匹配情況,每次i+1的結果只依賴與i 與j,在程式中可以設定二重迴圈,則此時二維陣列可以降為一維

dp[i+1]=dp[i]&&(p[j]=='?'||p[j]==s[i]) when p[j]!='*'

當p[j]=='*'時,在次情形下,可以匹配任何情況,即只要遍歷dp,發現dp[i]=1,則之後的結果肯定都是1

程式碼如下:

bool isMatch(string s,string p){
if(s.size()<1&&p.size()<1)return 1;
int slen=s.size();int plen=p.size();
int i=0;
int num=0;
while(i<plen){if(p[i]=='*')num++;i++;}
if(plen-num>slen)return 0;
vector<bool> dp(slen+1,false);
dp[0]=true;
for(int j=0;j<plen;j++){
    if(p[j]!='*'){
	for(i=slen;i>=0;i--)//以為後面更新了dp[0],故不能從i=0開始
	    dp[i+1]=dp[i]&&(p[j]==s[i]||p[j]=='?');//此處體現i依賴與j
                  }
    else {
	i=0;
	while(i<=slen&&!dp[i])i++;
	for(;i<=slen;i++)dp[i]=1;
          }
    dp[0]=dp[0]&&(p[j]=='*');//更新dp[0],因為dp[0]表示s是空串,只有是星才匹配
                        }
return dp[slen];
}