力扣第115题 不同的子序列 c++ 动态规划 注释版 + Java代码
题目
困难
相关标签
给你两个字符串 s
和 t
,统计并返回在 s
的 子序列 中 t
出现的个数,结果需要对 109 + 7 取模。
示例 1:
输出:3
rabbbit
rabbbit
rabbbit
示例 2:
输出:5
babgbag
babgbag
babgbag
babgbag
babgbag
提示:
1 <= s.length, t.length <= 1000
s
和t
由英文字母组成
思路和解题方法
创建dp数组:在这段代码中,我们使用了一个二维的dp数组来保存子问题的解。数组的大小是(s.size() + 1)×(t.size() + 1),其中s.size()表示字符串s的长度,t.size()表示字符串t的长度。所以dp数组的行数是s的长度加1,列数是t的长度加1。
初始化边界条件:在动态规划的问题中,通常需要初始化一些特殊情况下的解。在这段代码中,我们初始化了dp数组的第一行和第一列。根据题目要求,当t为空串时,s中只有一个空串与之匹配,所以dp[i][0]的初始值都是1。另外,当s为空串时,任何非空的t都无法与之匹配,所以dp[0][j]的初始值都是0,其中j从1到t的长度。
填充dp数组:通过两层循环遍历dp数组,我们可以逐步计算出dp[i][j]的值。循环中的i表示s的索引,j表示t的索引。在每次迭代中,我们比较s[i-1]和t[j-1]的字符:
如果s[i-1]等于t[j-1],说明当前字符能够匹配,此时有两种选择:
- 使用s[i-1]匹配t[j-1]:即dp[i][j] = dp[i-1][j-1],表示之前的方案数加上这种选择。
- 不使用s[i-1]匹配t[j-1]:即dp[i][j] += dp[i-1][j],表示继承之前的方案数。
如果s[i-1]不等于t[j-1],说明当前字符不能够匹配,此时只能不使用s[i-1],故有dp[i][j] = dp[i-1][j]。
返回结果:最后,我们返回dp[s.size()][t.size()],即s中有多少个子序列与t相同的方案数。
复杂度
时间复杂度:
O(n*m)
时间复杂度:
- 遍历两个字符串,共需执行 s.size() * t.size() 次循环。
- 在每个循环中,进行常数时间(O(1))的比较和赋值操作。 所以,总体时间复杂度为 O(s.size() * t.size())。
空间复杂度
O(n*m)
空间复杂度:
使用了一个二维数组 dp,大小为 (s.size() + 1) * (t.size() + 1),空间复杂度为 O(s.size() * t.size())。
c++ 代码
int numDistinct(string s, string t) {
// 创建一个二维数组 dp,用于存储状态转移值
vector<vector<unsigned long long>> dp(s.size() + 1, vector<unsigned long long>(t.size() + 1));
// 初始化边界条件
for (int i = 0; i < s.size(); i++) {
dp[i][0] = 1;
}
for (int j = 1; j < t.size(); j++) {
dp[0][j] = 0;
}
// 计算状态转移值
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
// 返回最后一个状态转移值,即字符串 t 在字符串 s 中出现的不同子序列的个数
return dp[s.size()][t.size()];
}
Java代码
class Solution {
public int numDistinct(String s, String t) {
// 创建一个二维数组dp,大小为(s.length() + 1) * (t.length() + 1)
int[][] dp = new int[s.length() + 1][t.length() + 1];
// 初始化边界条件,当t为空字符串时,s中的任意子串都可以匹配,所以dp[i][0] = 1
for (int i = 0; i < s.length() + 1; i++) {
dp[i][0] = 1;
}
// 动态规划计算dp数组的值
for (int i = 1; i < s.length() + 1; i++) {
for (int j = 1; j < t.length() + 1; j++) {
// 如果当前s的字符与t的字符相等,说明可以选择匹配或者不匹配
if (s.charAt(i - 1) == t.charAt(j - 1)) {
// 若选择匹配,则需要找到前面子串的匹配数,即dp[i - 1][j - 1]
// 若选择不匹配,则需要找到前面子串的匹配数,即dp[i - 1][j]
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
// 如果当前s的字符与t的字符不相等,则无法匹配,所以匹配数与前面子串相同,即dp[i - 1][j]
dp[i][j] = dp[i - 1][j];
}
}
}
// 返回dp数组右下角的值,即整个s和t的匹配数
return dp[s.length()][t.length()];
}
}
觉得有用的话可以点点赞,支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)