给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
测试用例保证结果在 32 位有符号整数范围内。
示例 1:
输入:s = "rabbbit", t = "rabbit" 输出:3 解释: 如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。rabbbit rabbbit rabbbit示例 2: 输入:s = "babgbag", t = "bag" 输出:5 解释:如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。babgbagbabgbagbabgbagbabgbagbabgbag
以下给出解法套路和五种实现。
【五种实现】
- 常规二维dp
- 交替滚动一维dp
- 原地滚动一维dp(借助变量正序版)
- 原地滚动一维dp(逆序版)
- 原地滚动一维dp(逆序版,最后一行只更新最终答案)
【解法套路四步走】
1)根据问题给出二维dp数组定义。
要求s子序列中t的个数。立刻定义dp[i][j]: s的前i个字符中的t的前j个字符的子序列个数
2)分别令两个维度为0,推测边界。
只有当j=0时,才有dp[0][j] = 1,否则dp[0][j] = 0。
dp[i][0] = 1。
3)寻找转移方程。
若s[i] ≠ t[j] 转移方程:
dp[i][j] = dp[i-1][j]
若s[i] = t[j]:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
4)空间压缩。
也是固定套路,先从二维数组转两个一维数组(交替滚动),再从两个一维数组转一个一维数组(原地滚动)。可借助变量来实现正序一维滚动dp,也可以不借助变量直接一维逆序滚动dp。
下面给出代码。
==== 二维dp ====
class Solution {
public int numDistinct(String s, String t) {
if(s.length() < t.length()) return 0; // s长度小于t时,s中不会出现t
int ns = s.length(), nt = t.length();
char[] chss = s.toCharArray(), chst = t.toCharArray();
int[][] dp = new int[ns + 1][nt + 1];
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++){
dp[i][j] = dp[i - 1][j] + (chss[i - 1] == chst[j - 1] ? dp[i - 1][j - 1] : 0);
}
}
return dp[ns][nt];
}
}
==== 交替滚动一维dp ====
class Solution {
public int numDistinct(String s, String t) {
if(t.length() > s.length()) return 0; // s长度小于t时间直接返回0
int ns = s.length(), nt = t.length();
int[] pre = new int[nt + 1], cur = new int[nt + 1];
char[] chss = s.toCharArray(), chst = t.toCharArray();
pre[0] = 1;
cur[0] = 1; // 当t为空串时,此行是必须的
for(int i = 1; i <= ns; i++){
for(int j = 1; j <= nt; j++){
if(chss[i - 1] == chst[j - 1]) cur[j] = pre[j - 1] + pre[j];
else cur[j] = pre[j];
}
pre = Arrays.copyOf(cur, nt + 1); // for(int k = 0; k <= nt; k++) pre[k] = cur[k];
}
return cur[nt];
}
}
==== 原地滚动一维dp 【借助变量正序】====
class Solution {
public int numDistinct(String s, String t) {
if(s.length() < t.length()) return 0; // s长度小于t时,s中不会出现t
int ns = s.length(), nt = t.length();
char[] chss = s.toCharArray(), chst = t.toCharArray();
int[] dp = new int[nt + 1];
dp[0] = 1;
int pre = dp[0];
for(int i = 1; i <= ns; i++){
for(int j = 1; j <= nt; j++){
int nextPre = dp[j]; // 相当于二维矩阵中待更新的dp[i][j]的dp[i - 1][j - 1]
if(chss[i - 1] == chst[j - 1] ) dp[j] += pre;
pre = nextPre;
}
pre = 1; // 每完成一行要重置该值为1
}
return dp[nt];
}
}
==== 原地滚动一维dp 【逆序(无需变量辅助)】====
class Solution {
public int numDistinct(String s, String t) {
if(s.length() < t.length()) return 0; // s长度小于t时,s中不会出现t
int ns = s.length(), nt = t.length();
char[] chss = s.toCharArray(), chst = t.toCharArray();
int[] dp = new int[nt + 1];
dp[0] = 1;
for(int i = 1; i <= ns; i++){
for(int j = nt; j > 0; j--){
if(chss[i - 1] == chst[j - 1] ) dp[j] += dp[j - 1];
}
}
return dp[nt];
}
}
==== 原地滚动一维dp 【最后一行只更新dp[nt]】====
class Solution {
public int numDistinct(String s, String t) {
if(s.length() < t.length()) return 0; // s长度小于t时,s中不会出现t
int ns = s.length(), nt = t.length();
char[] chss = s.toCharArray(), chst = t.toCharArray();
int[] dp = new int[nt + 1];
dp[0] = 1;
for(int i = 1; i < ns; i++){ // 省一行
for(int j = nt; j > 0; j--){
if(chss[i - 1] == chst[j - 1] ) dp[j] += dp[j - 1];
}
}
return chss[ns - 1] == chst[nt - 1] ? dp[nt] + dp[nt - 1] : dp[nt]; // 最后一行只需处理dp[nt]
}
}
需注意:
本题将 s 放置于 行的位置还是 列的位置 非常重要,这决定了转移方程怎么写,也决定了用一维原地滚动的时候,是按行遍历 还是按列遍历。
上面的一维原地滚动 是按行遍历的,如果将s换个位置放,原地滚动 就需要按列遍历了。
【按行遍历指的是:一行一行遍历;按列遍历指的是:一列一列遍历】

809

被折叠的 条评论
为什么被折叠?



