題目鏈接 LeetCode 115. 不同的子序列
題目描述
給定一個字符串 S 和一個字符串 T,計算在 S 的子序列中 T 出現的個數。
一個字符串的一個子序列是指,通過刪除一些(也可以不刪除)字符且不干擾剩餘字符相對位置所組成的新字符串。(例如,“ACE” 是 “ABCDE” 的一個子序列,而 “AEC” 不是)
題目數據保證答案符合 32 位帶符號整數範圍。
示例 1:
輸入:S = “rabbbit”, T = “rabbit”
輸出:3
解釋:
如下圖所示, 有 3 種可以從 S 中得到 “rabbit” 的方案。
(上箭頭符號 ^ 表示選取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例 2:
輸入:S = “babgbag”, T = “bag”
輸出:5
解釋:
如下圖所示, 有 5 種可以從 S 中得到 “bag” 的方案。
(上箭頭符號 ^ 表示選取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
解題思路
當初搞信息競賽的時候對DP掌握的就很差,現在得重新來過了,所以我們就從一開始的遞歸解法開始吧。
很多DP題目都可用遞歸來做,當然不能過掉全部的數據,但是我們可以用遞歸來找思路,然後再轉化成記憶化搜索,最後優化成DP,是一個很好的思路。
首先得搞清楚一個問題,不論是遞歸還是DP,都要搞清楚這個問題所涉及的所有狀態,哪些狀態是對提供答案有用的,哪些是沒用的,如果狀態不清楚的話就很難進行下一步操作。
在這道題中,我們先來找哪些狀態可以對提供答案有用。
①如果s[i]==t[j]
我們有兩種選擇
第一種即當前s[i]和t[j]相匹配,兩個指針都往下一個移動。
第二種就是不用當前的s[i]和t[j]匹配,而是用下一個s[i+1]和t[j]去匹配,因爲有可能出現 i 位置上和 i+1 位置上都是相同的,可以有選擇的用哪一個來匹配。
②如果s[i]!=t[j]
那麼 i 指針往下一個移動, j 指針不移動。
根據這幾個狀態我們就可以寫出遞歸的程序,進而也就可以寫出DP的程序代碼。
程序代碼
遞歸版
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int dfs(string s,string t,int i,int j) {
if(j==t.size()) return 1;
if(i==s.size()) return 0;
if(s[i]!=t[j]) return dfs(s,t,i+1,j);
else if(s[i]==t[j]) return ( dfs(s,t,i+1,j) + dfs(s,t,i+1,j+1) ) ;
}
int main () {
string s,t;
cin>>s>>t;
int ns=s.length();
int nt=t.length();
int ans=dfs(s,t,0,0);
cout<<ans<<endl;
return 0;
}
DP版
#include<cmath>
#include<cstdio>
using namespace std;
string s,t;
int main() {
cin>>s>>t;
int ns=s.length();
int nt=t.length();
int dp[ns+1][nt+1];
for(int j=0;j<=nt;++j) dp[0][j]=0;
for(int i=0;i<=ns;++i) dp[i][0]=1;
for(int i=1;i<=ns;++i)
for(int j=1;j<=nt;++j)
if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
else dp[i][j]=dp[i-1][j];
cout<<dp[ns][nt]<<endl;
return 0;
}