欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 力扣第115题:不同的子序列 — C语言解法

力扣第115题:不同的子序列 — C语言解法

2025/4/2 14:51:55 来源:https://blog.csdn.net/weixin_48941116/article/details/144688248  浏览:    关键词:力扣第115题:不同的子序列 — C语言解法

力扣第115题:不同的子序列 — C语言解法

题目描述

给定一个字符串 s 和一个字符串 t,返回 ts 中出现的不同子序列的个数。

子序列 是由字符串中删除一些字符(可以不删除任何字符)得到的一个新字符串。

说明:

  • 字符串 st 的长度均不超过 1000 1000 1000
  • 一个子序列是由删除零个或多个字符而得到的字符串。

解题思路

这道题目要求我们计算字符串 t 在字符串 s 中出现的不同子序列的个数。子序列的匹配可以是不连续的,但必须按顺序匹配。

动态规划(Dynamic Programming)

这是一道典型的动态规划问题。设 dp[i][j] 表示字符串 s 的前 i i i 个字符中,字符串 t 的前 j j j 个字符的不同子序列的个数。

状态转移方程
  1. 当字符匹配时( s [ i − 1 ] = = t [ j − 1 ] s[i-1] == t[j-1] s[i1]==t[j1]

    • 我们有两种选择:
      • 包含当前字符:即在当前字符 s[i-1] 匹配到 t[j-1] 后,继续计算剩余部分:dp[i-1][j-1]
      • 不包含当前字符:即忽略当前字符 s[i-1],继续计算:dp[i-1][j]
    • 所以状态转移方程为:
      d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j-1] + dp[i-1][j] dp[i][j]=dp[i1][j1]+dp[i1][j]
  2. 当字符不匹配时( s [ i − 1 ] ≠ t [ j − 1 ] s[i-1] \neq t[j-1] s[i1]=t[j1]

    • 我们只能选择不包含当前字符 s[i-1],即:
      d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]
边界条件
  • dp[i][0] = 1:对于任何 i i i,空字符串 t 总能在字符串 s 中找到(即不选任何字符)。
  • dp[0][j] = 0:对于任何 j > 0 j > 0 j>0,空字符串 s 无法包含任何非空字符串 t
模运算

由于子序列的个数可能非常大,我们需要对结果进行取模运算。通常使用 M O D = 1 0 9 + 7 MOD = 10^9 + 7 MOD=109+7,它是一个大素数,可以避免整数溢出,并且在算法竞赛中常见。


代码实现

#include <stdio.h>
#include <string.h>#define MOD 1000000007  // 定义常用的模数// 动态规划实现:计算 t 在 s 中的不同子序列个数
long long numDistinct(char* s, char* t) {int m = strlen(s);int n = strlen(t);// dp[i][j] 表示 s[0..i-1] 中 t[0..j-1] 的不同子序列个数long long dp[m + 1][n + 1];// 初始化 dp 数组for (int i = 0; i <= m; i++) {dp[i][0] = 1;  // 空字符串 t 总能在任何 s 中找到}for (int j = 1; j <= n; j++) {dp[0][j] = 0;  // 非空字符串 t 不能在空字符串 s 中找到}// 填充 dp 数组for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (s[i - 1] == t[j - 1]) {// 如果 s[i-1] == t[j-1],我们可以选择包含 s[i-1] 或不包含dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % MOD;} else {// 如果 s[i-1] != t[j-1],我们只能选择不包含 s[i-1]dp[i][j] = dp[i - 1][j] % MOD;}}}// 返回 dp[m][n],即 s[0..m-1] 中 t[0..n-1] 的不同子序列个数return dp[m][n];
}// 测试代码
int main() {char s[] = "rabbbit";char t[] = "rabbit";long long result = numDistinct(s, t);printf("Number of distinct subsequences: %lld\n", result);return 0;
}

代码解析

1. 定义模数

我们在代码中定义了常量 MOD = 1000000007,用于防止结果的溢出。每次更新 dp 数组时,我们都对当前值取模,确保它不会超出 long long 类型的最大值。

#define MOD 1000000007  // 定义常用的模数

2. 初始化 dp 数组

在动态规划中,我们使用一个二维数组 dp 来存储不同子序列的计数。dp[i][j] 表示在 s[0..i-1] 中找到 t[0..j-1] 的不同子序列的个数。我们初始化 dp[i][0] = 1,因为空字符串 t 总是能够在 s 中找到(即不选择任何字符)。同时,对于任何 j > 0dp[0][j] = 0,因为非空字符串 t 无法在空字符串 s 中找到。

for (int i = 0; i <= m; i++) {dp[i][0] = 1;  // 空字符串 t 总能在任何 s 中找到
}
for (int j = 1; j <= n; j++) {dp[0][j] = 0;  // 非空字符串 t 不能在空字符串 s 中找到
}

3. 填充 dp 数组

我们按照动态规划的思路,遍历 st 的每个字符,更新 dp[i][j]。如果 s[i-1] == t[j-1],我们可以选择包含当前字符或不包含它,更新为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j-1] + dp[i-1][j] dp[i][j]=dp[i1][j1]+dp[i1][j]
否则,我们只能选择不包含当前字符:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]

每次更新时,我们都对结果取模,防止溢出。

for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (s[i - 1] == t[j - 1]) {dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % MOD;} else {dp[i][j] = dp[i - 1][j] % MOD;}}
}

4. 返回结果

最终,dp[m][n] 存储的是在 s[0..m-1] 中,t[0..n-1] 的不同子序列的个数。

return dp[m][n];

5. 测试代码

main 函数中,我们测试了 s = "rabbbit"t = "rabbit" 的情况,输出了结果:

long long result = numDistinct(s, t);
printf("Number of distinct subsequences: %lld\n", result);

时间复杂度与空间复杂度

  • 时间复杂度 O ( m × n ) O(m \times n) O(m×n),其中 m m m n n n 分别是字符串 st 的长度。我们需要填充一个 m × n m \times n m×n 的动态规划数组。
  • 空间复杂度 O ( m × n ) O(m \times n) O(m×n),用于存储动态规划数组 dp

由于每次更新 dp[i][j] 时的计算都是常数时间,因此总时间复杂度为 O ( m × n ) O(m \times n) O(m×n)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词