LeetCode/不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数
1. 递归
题目其实就是要求我们在s中从前往后挑选字符来匹配子序列,得到最后的匹配次数
也就是成功匹配的递归分支
class Solution {
public:
int numDistinct(string s, string t) {
return traceback(s,t,0,0);//从初始位置开始递归,从前往后
}
int traceback(string &s, string &t,int i,int j){
if(i>t.size()) return 1;//base case,先判断该条件
if(j>s.size()) return 0;//base case,s中字符选完
if(t[i]==s[j]) return traceback(s,t,i+1,j+1)+traceback(s,t,i,j+1);//如果可以匹配,选择匹配和跳过两种分支
else return traceback(s,t,i,j+1);//不能匹配跳过该字符
}
};
2. 记忆化搜索
方法一递归的优化,减少重复递归
从前往后(带边界)
class Solution {
public:
int numDistinct(string s, string t) {
int m =t.size(); int n = s.size();
vector<vector<int>> memo(m+1,vector<int>(n+1,-1));
for(int i=0;i<m+1;i++)
memo[i][n]= 0;//竖直边界
for(int j=0;j<n+1;j++)
memo[m][j]= 1; //横向边界,后算把前面的覆盖掉
return traceback(s,t,0,0,memo);//从前往后
}
int traceback(string &s, string &t,int i,int j,vector<vector<int>> &memo){
if(memo[i][j]!=-1) return memo[i][j];//跳过该字符
if(t[i]==s[j])
memo[i][j] = traceback(s,t,i+1,j+1,memo)+traceback(s,t,i,j+1,memo);
else memo[i][j] = traceback(s,t,i,j+1,memo);
return memo[i][j];
}
};
从前往后
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<int>> memo(t.size(),vector<int>(s.size(),-1));
return traceback(s,t,0,0,memo);//从前往后
}
int traceback(string &s, string &t,int i,int j,vector<vector<int>> &memo){
if(i>t.size()-1) return 1;//边界条件
if(j>s.size()-1) return 0;//边界条件
if(memo[i][j]!=-1) return memo[i][j];//跳过该字符
if(t[i]==s[j])
memo[i][j] = traceback(s,t,i+1,j+1,memo)+traceback(s,t,i,j+1,memo);
else memo[i][j] = traceback(s,t,i,j+1,memo);
return memo[i][j];
}
};
从后往前
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<int>> memo(t.size(),vector<int>(s.size(),-1));
return traceback(s,t,t.size()-1,s.size()-1,memo);
}
int traceback(string &s, string &t,int i,int j,vector<vector<int>> &memo){
if(i<0) return 1;
if(j<0) return 0;
if(memo[i][j]!=-1) return memo[i][j];
if(t[i]==s[j])
memo[i][j] = traceback(s,t,i-1,j-1,memo)+traceback(s,t,i,j-1,memo);
else memo[i][j] = traceback(s,t,i,j-1,memo);
return memo[i][j];
}
};
3. 动态规划
考虑从前往后递推,与前面带边界递归基本一致
边界条件为dp[0][j]=1,dp[i][0]=0
状态转移方程
\[dp[i][j]=\begin{cases}
dp[i-1][j-1]+dp[i][j-1], &t[i]==s[j]\\
dp[i][j-1], & t[i]!=s[j]\\
\end{cases}
\]
class Solution {
public:
int numDistinct(string s, string t) {
int m =t.size(); int n = s.size();
vector<vector<unsigned int>> dp(m+1,vector<unsigned int>(n+1,0));
//for(int i=0;i<m+1;i++)
//dp[i][0]= 0; //竖向边界
for(int j=0;j<n+1;j++)
dp[0][j]= 1; //横向边界,把前面的覆盖
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(t[i-1]==s[j-1]) //这里需要左移比较,因为加了左边界,在dp中i表示第i个字符
dp[i][j] = dp[i-1][j-1] + dp[i][j-1];
else dp[i][j] = dp[i][j-1];
}}
return dp[m][n];
}
};
4. 动态规划(一维优化)
class Solution {
public:
int numDistinct(string s, string t) {
int m =t.size(); int n = s.size();
vector<unsigned int> dp(m+1,0);
dp[0]=1;
for(int j=1;j<=n;j++)//从第一列开始
for(int i=m;i>0;i--)//从下往上遍历,复用前一列上一行的数
if(t[i-1]==s[j-1])
dp[i] = dp[i-1] + dp[i];
return dp[m];
}
};