力扣每日一题115:不同的子序列

题目

困难

给你两个字符串 st ,统计并返回在 s子序列t 出现的个数,结果需要对 109 + 7 取模。

示例 1:

复制代码
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
++rabb++b++it++
++ra++b++bbit++
++rab++b++bit++

示例 2:

复制代码
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
++ba++b**g**bag
++ba++bgba++g++
**b**abgb++ag++
ba**b**gb**ag**
babg++bag++

提示:

  • 1 <= s.length, t.length <= 1000
  • st 由英文字母组成

面试中遇到过这道题?

1/5

通过次数

177K

提交次数

340.8K

通过率

51.9%

思路

注意!这是一道hard题!

看到xxx序列在xxx序列中xxx的个数、序列的最长xxx子序列、最长xxx串,最短xxx串这些题目,一眼就想到动态规划。

序列t在序列s的子序列中出现的次数,出现了两个序列的匹配,毫无疑问是二维dp,我们采用由前向后dp的思路,dp[i][j]来表示 t[1:i] 在 s[1:j] 的子序列中出现的次数。注意!由于动态规划一般要对边界值进行讨论,dp的下标从[1][1]开始,[0][......]和[......][0]用来做边界值的讨论。

对于这种两个字符串的动态规划,一般都是讨论 [i] 和 [j] 各自对应的字符是否相等来分类。

状态转移

1、如果t[i-1]!=s[j-1],也就是 i 对应的字符和 j 对应的字符不相等的情况。

要实现 t[1:i] 和 s[1:j] 的匹配,因为两部分切片的最后一个字符不相等,所有只能祈求 t[1:i] 能不能和 s[1:j-1] 能不能匹配。dp[i][j]=dp[i][j-1]。

2、t[i-1]==s[j-1]

两部分切片的最后一个字符相等,所以我们既可以选择两种匹配方法

a、t[i-1] 和 s[j-1] 匹配完成后,t[1:i-1]和t[1:j-1]匹配,这一部分的方案数量是 dp[i-1][j-1]

b、t[i-1]不和s[j-1]匹配,这时就和t[i-1]!=s[j-1]一样,这一部分方案数量就是 dp[i][j-1]

dp[i][j]=dp[i-1][j-1]+dp[i][j-1]

边界情况的讨论。

状态转移方程中,我们要用到dp[i-1][j-1],而我们的状态转移的循环是

复制代码
        for(int i=1;i<=m;i++){
            for(int j=i;j<=n;j++){
                if(t[i-1]!=s[j-1]){
                    dp[i][j]=dp[i][j-1];
                }else{//t[i-1]和s[j-1]匹配+t[i-1]不和s[j-1]匹配
                    dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
                }
            }
        }

m是t串的长度,n是s串的长度。

从循环上看,j<i||i==0 这一部分是没有出现在循环里的,但是i-1和j-1能遍历到,所以我们必然要讨论。

i=0时,代表切片为空串,空串是任何串的子串,所以这一部分的初值为1。

j<i时,s串长度小于t串,这是不可能是子串,所以这一部分初值为0。

复制代码
        vector<vector<unsigned long long>> dp(m+1,vector<unsigned long long>(n+1,0));
        //边界情况-->空字符串是任何字符串的子序列
        for(int i=0;i<=n;i++){
            dp[0][i]=1;
        }

代码(注意数据范围和取模)

cpp 复制代码
class Solution {
public:
    const int mod=1e9+7;
    int numDistinct(string s, string t) {
        int n=s.length();
        int m=t.length();
        if(n<m) return 0;
        //dp[i][j]-->t[1-i]在s[1-j]中出现的次数()
        //防止边界条件判断,从[1][1]开始算
        //long long都会超出数据范围
        vector<vector<unsigned long long>> dp(m+1,vector<unsigned long long>(n+1,0));
        //边界情况-->空字符串是任何字符串的子序列
        for(int i=0;i<=n;i++){
            dp[0][i]=1;
        }
        for(int i=1;i<=m;i++){
            for(int j=i;j<=n;j++){
                if(t[i-1]!=s[j-1]){
                    dp[i][j]=dp[i][j-1];
                }else{//t[i-1]和s[j-1]匹配+t[i-1]不和s[j-1]匹配
                    dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
                }
            }
        }
        int ans=dp[m][n]%mod;
        return ans;
    }
};
相关推荐
Liudef062 分钟前
基于HTML与Java的简易在线会议系统实现
java·前端·html
JosieBook12 分钟前
【Java编程动手学】Java常用工具类
java·python·mysql
oioihoii15 分钟前
C++11标准库算法:深入理解std::none_of
java·c++·算法
老虎062733 分钟前
数据结构(Java)--位运算
java·开发语言·数据结构
yanjiaweiya34 分钟前
云原生-集群管理续
java·开发语言·云原生
Swift社区35 分钟前
Swift 解 LeetCode 320:一行单词有多少种缩写可能?用回溯找全解
开发语言·leetcode·swift
写不出来就跑路40 分钟前
暑期实习感悟与经验分享:从校园到职场的成长之路
java·开发语言·经验分享·spring boot
No Silver Bullet41 分钟前
软件工程功能点估算法常用术语介绍
java·开发语言·软件工程
泰勒疯狂展开44 分钟前
Java研学-MongoDB(二)
java·mongodb
karmueo463 小时前
视频序列和射频信号多模态融合算法Fusion-Vital解读
算法·音视频·多模态