115. 不同的子序列

Desciption

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。

0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成

示例:

输入:s = "babgbag", t = "bag"
输出:5
解释:
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
babgbag
babgbag
babgbag
babgbag
babgbag

Solution

这种计算数量的题目,可以先考虑dp。

dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符,字符串 t 在 s 的出现次数,思考一下怎么转移?额,好像转移的方程式很难写(写不太出来)

可能我们的 dp 数组的状态表示的不太正确,重新思考一下

dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符,t 的前 j 个字符 在 s 的前 i 个字符中的出现次数。思考怎么转移?

如果 s[i] == t[j],那么 dp[i][j] 可以由两种情况转移过来:

  • s[i] 和 t[j] 进行匹配,则可以由 s 的前 i-1 个字符和 t 的前 j-1 个字符匹配的数量转移过来,即 dp[i-1][j-1]
  • s[i] 和 t[j] 不进行匹配,则可以由 s 的前 i-1 个字符和 t 的前 j 个字符匹配的数量转移过来,即 dp[i-1][j]

这里是否可以由 dp[i][j-1] 转移到 dp[i][j] 呢?应该明显不行,看看dp数组的含义就知道

如果 s[i] != t[j],那么 dp[i][j] 只能够由 s 的前 i-1 个字符和 t 的前 j 个字符匹配的数量转移过来,即 dp[i-1][j]

边界状态:dp[i][0] = 1 (0 <= i <= n)

Code

    public int numDistinct(String s, String t) {
        int n = s.length(), m = t.length();
        int[][] dp = new int[1010][m + 10];
        for (int i = 0; i < 1010; ++i) {
            Arrays.fill(dp[i], 0);
        }
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
                System.out.println(" i = " + i + " j = " + j + " dp[i][j] = " + dp[i][j]);
            }
        }
        return dp[n][m];
    }