统计不同子序列出现的个数
在本文中,我们将讨论如何解决一个有趣的问题:给定两个字符串s
和t
,我们需要统计t
在s
的子序列中出现的个数。最终的结果需要对 1 0 9 + 7 10^9 + 7 109+7 取模。
问题描述
我们被要求统计字符串t
在字符串s
的子序列中出现的次数。这意味着我们需要在字符串s
中找到所有可能的子序列,这些子序列可以通过删除某些字符来获得,并且这些子序列与字符串t
相等。
动态规划的思路
为了解决这个问题,我们可以使用动态规划。我们定义一个二维数组 dp
,其中 dp[i][j]
表示字符串 t
的前 i
个字符在字符串 s
的前 j
个字符的子序列中出现的个数。
接下来,我们可以考虑 dp[i][j]
的计算方式:
- 如果
t[i-1] == s[j-1]
,那么有两种情况:- 我们可以选择不使用
s[j-1]
,也就是说s[j-1]
不贡献到子序列中,那么dp[i][j] = dp[i][j-1]
。 - 我们可以选择使用
s[j-1]
,也就是说s[j-1]
贡献到子序列中,那么dp[i][j] = dp[i-1][j-1]
。
- 我们可以选择不使用
- 如果
t[i-1] != s[j-1]
,那么s[j-1]
无法贡献到子序列中,因此dp[i][j] = dp[i][j-1]
。
最终,我们可以得到状态转移方程如下:
dp[i][j] = dp[i][j-1] + (t[i-1] == s[j-1] ? dp[i-1][j-1] : 0)
示例
让我们使用示例来说明上述思路。
示例 1
s = "rabbbit"
t = "rabbit"
首先,我们创建一个二维数组 dp
,并初始化如下:
r a b b b i t
r 1 1 1 1 1 1 1
a 0
b 0
b 0
i 0
t 0
现在我们按照状态转移方程填充 dp
数组:
-
dp[1][1]
:考虑字符 ‘r’ 和 ‘r’。它们相等,所以dp[1][1] = dp[0][0] = 1
。 -
dp[1][2]
:考虑字符 ‘r’ 和 ‘ra’。它们不相等,所以dp[1][2] = dp[1][1] = 1
。 -
dp[1][3]
:考虑字符 ‘r’ 和 ‘rab’。它们不相等,所以dp[1][3] = dp[1][2] = 1
。 -
dp[1][4]
:考虑字符 ‘r’ 和 ‘rabb’。它们不相等,所以dp[1][4] = dp[1][3] = 1
。 -
dp[1][5]
:考虑字符 ‘r’ 和 ‘rabbb’。它们不相等,所以dp[1][5] = dp[1][4] = 1
。 -
dp[1][6]
:考虑字符 ‘r’ 和 ‘rabbbi’。它们不相等,所以dp[1][6] = dp[1][5] = 1
。 -
dp[1][7]
:考虑字符 ‘r’ 和 ‘rabbbit’。它们不相等,所以dp[1][7] = dp[1][6] = 1
。
现在,我们填充第一行,然后按照相同的方式填充其余行。
最终,dp[6][7]
表示字符串 “rabbit” 在字符串 “rabbbit” 的子序列中出现的次数。结果为 dp[6][7] = 3
。
示例 2
s = "babgbag"
t = "bag"
我们执行类似的动态规划过程,填充 dp
数组。
最终,dp[3][7]
表示字符串 “bag” 在字符串 “babgbag” 的子序列中出现的次数。结果为 dp[3][7] = 5
。
Java 代码实现
现在,让我们将上述思路转化为 Java 代码:
class Solution {public int numDistinct(String s, String t) {int m = s.length();int n = t.length();int[][] dp = new int[n + 1][m + 1];// Initialize the first row with 1'sfor (int j = 0; j <= m; j++) {dp[0][j] = 1;}for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {dp[i][j] = dp[i][j - 1];if (t.charAt(i - 1) == s.charAt(j - 1)) {dp[i][j] += dp[i - 1][j - 1];}}}return dp[n][m];}
}
这段代码实现了上述动态规划思路,并返回了t
在s
的子序列中出现的次数。