zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Leetcode No.115 不同的子序列(动态规划)

2023-03-20 14:56:10 时间

一、题目描述

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

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

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

提示:

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

二、解题思路

假设字符串 s 和 t 的长度分别为 m 和 n。如果 t 是 s 的子序列,则 s 的长度一定大于或等于 t 的长度,即只有当 m≥n 时,t 才可能是 s 的子序列。如果 m<n,则 t 一定不是 s 的子序列,因此直接返回 0。

当 m≥n 时,可以通过动态规划的方法计算在 s 的子序列中 t 出现的个数。

创建二维数组 dp,其中 dp[i][j] 表示在 s[i:]的子序列中 t[j:]出现的个数。

上述表示中,s[i:] 表示 s从下标 i 到末尾的子字符串,t[j:] 表示 t 从下标 j 到末尾的子字符串。

考虑动态规划的边界情况:

1、当 j=n时,t[j:] 为空字符串,由于空字符串是任何字符串的子序列,因此对任意0≤i≤m,有 dp[i][n]=1;

2、当 i=m且 j<n时,s[i:]为空字符串,t[j:] 为非空字符串,由于非空字符串不是空字符串的子序列,因此对任意0≤j<n,有dp[m][j]=0。

当 i<m 且 j<n 时,考虑 dp[i][j] 的计算:

1、当 s[i]=t[j]时,dp[i][j] 由两部分组成:

①如果 s[i] 和 t[j]匹配,则考虑 t[j+1:]作为 s[i+1:]的子序列,子序列数为 dp[i+1][j+1];

②如果 s[i]不和 t[j]匹配,则考虑 t[j:]作为 s[i+1:] 的子序列,子序列数为 dp[i+1][j]。

因此当 s[i]=t[j]时,有 dp[i][j]=dp[i+1][j+1]+dp[i+1][j]。

2、当 s[i] !=t[j] 时,s[i]不能和 t[j]匹配,因此只考虑 t[j:]作为 s[i+1:]的子序列,子序列数为dp[i+1][j]。

因此当 s[i] !=t[j] 时,有 dp[i][j]=dp[i+1][j]。

由此可以得到如下状态转移方程:

最终计算得到 dp[0][0] 即为在 s 的子序列中 t 出现的个数。

三、代码

class Solution2 {
    public int numDistinct(String s, String t) {
        int m = s.length(), n = t.length();
        //如果t是s的子序列,则s的长度一定大于或等于t的长度,如果 m<n,则t一定不是s的子序列,因此直接返回 0。
        if (m < n) {
            return 0;
        }
        //s[i:] 表示 s从下标 i 到末尾的子字符串,t[j:] 表示 t 从下标 j 到末尾的子字符串。
        //dp[i][j] 表示在 s[i:]的子序列中 t[j:]出现的个数,默认值全为0
        int[][] dp = new int[m + 1][n + 1];
        //当 i=m且 j<n时,s[i:]为空字符串,t[j:] 为非空字符串,由于非空字符串不是空字符串的子序列,因此对任意0≤j<n,有dp[m][j]=0。
        //当 j=n时,t[j:] 为空字符串,由于空字符串是任何字符串的子序列,因此对任意0≤i≤m,有 dp[i][n]=1;
        for (int i = 0; i <= m; i++) {
            dp[i][n] = 1;
        }
        for (int i = m - 1; i >= 0; i--) {
            char sChar = s.charAt(i);
            for (int j = n - 1; j >= 0; j--) {
                char tChar = t.charAt(j);
                //当 s[i]=t[j]时
                if (sChar == tChar) {
                    //①如果 s[i] 和 t[j]匹配,则考虑 t[j+1:]作为 s[i+1:]的子序列,子序列数为 dp[i+1][j+1];
                    //②如果 s[i]不和 t[j]匹配,则考虑 t[j:]作为 s[i+1:] 的子序列,子序列数为 dp[i+1][j]。
                    dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
                } else {
                    //s[i]!=t[j] 时,s[i]不能和 t[j]匹配,因此只考虑 t[j:]作为 s[i+1:]的子序列,子序列数为dp[i+1][j]
                    dp[i][j] = dp[i + 1][j];
                }
            }
        }
        return dp[0][0];
    }
    public static void main(String[] args) {
        Solution2 solution2=new Solution2();
        System.out.println(solution2.numDistinct("rabbbit","rabbit"));
    }
}

四、复杂度分析

时间复杂度:O(mn),其中 m 和 n 分别是字符串 s 和 t 的长度。二维数组 dp 有 m+1 行和 n+1 列,需要对dp 中的每个元素进行计算。

空间复杂度:O(mn),其中 m 和 n 分别是字符串 s 和 t 的长度。创建了 m+1 行 n+1列的二维数组 dp。