leetcode:115. 不同的子序列

本文介绍了如何使用递归和动态规划解决LeetCode 115(不同子序列)和Offer II 097(子序列数目)的问题。通过实例解析和代码实现,展示了如何构建状态转移方程,以及如何在标准样本对应模型下计算字符串子序列的数目。
摘要由CSDN通过智能技术生成

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int numDistinct(string s, string t) {

    }
};

题目解析

分析数据量

  • 0 <= s.length, t.length <= 1000:行列对应模型,(10^6)
  • s 和 t 由英文字母组成:maybe数组作为桶

分析题意

在s串上“挑选”字符,去匹配t串的字符,求挑选的方法数

暴力递归

抓住“选”,s要按照t来挑选,逐个字符考察选或者不选,分别来到什么状态

举例,s 为babgbag,t 为bag,末尾字符相同,于是 s 有两种选择:

  • s [ s . l e n g t h − 1 ] s[s.length-1] s[s.length1]去匹配掉 t [ t . l e n g t h − 1 ] t[t.length-1] t[t.length1],问题规模缩小:继续考察 b a b g b a babgba babgba b a ba ba
  • 不这么做,但 t [ t . l e n g t h − 1 ] t[t.length-1] t[t.length1]仍需被匹配,于是在 b a b g b a babgba babgba中继续挑,考察 b a b g b a babgba babgba b a g bag bag

在这里插入图片描述
是否用它去匹配,是两种不同的挑选方式,各自做下去所产生的方式数,相加,是大问题的解。

现在我们拆解出规模小一点的子问题。完善一下,定义出递归函数:

返回:从开头到s[i]的子串中,出现『从开头到t[j]的子串』的次数。
即,从 前者 选字符,去匹配 后者,的方案数。

看了s[i]==t[j]的情况,那s[i]!=t[j]的情况呢?s[i]不匹配t[j],唯有拿s[i]之前的子串去匹配

现在两种情况下的递归公式都好写了。递归树底部的 base case 呢?

随着递归压栈,子问题规模(子串长度)在变小:

  • 小到 t 变成空串,此时 s 为了匹配它,方式只有1种:什么字符也不用挑(或 s 也是空串,什么都不做就匹配了,方式数也是1)
  • 小到 s 变成空串,但 t 不是,s 怎么也匹配不了 t,方式数为 0
class Solution {
    int process(string &s, string &t, int i, int j){
        if(j < 0){
            return 1;
        }
        
        if(i < 0){
            return 0;
        }
        
        if(s[i] == t[j]){
            return process(s, t, i - 1, j) + process(s, t, i - 1, j - 1);
        }
        
        return process(s, t, i - 1, j);
    }
public:
    int numDistinct(string s, string t) {
        int sLen = s.size(), tLen = t.size();
        
        return process(s, t, sLen - 1, tLen - 1);
    }
};

思路

标准的样本对应模型。

  • d p [ i ] [ j ] dp[i][j] dp[i][j]:s只拿前i个字符做子序列,有多少个子序列,字面值等于T的前j个字符的前缀串(样本对应模型是只看最后一个字符能不能要,要不要)
    • 可能性1)S[…i]的所有子序列中,都不以s[i]结尾,则dp[i][j]肯定包含dp[i-1][j]
    • 可能性2)S[…i]的所有子序列中,都必须以s[i]结尾,这要求S[i] == T[j],则dp[i][j]包含dp[i-1][j-1]
  • base case:
    • j = = 0 j==0 j==0时, d p [ i ] [ 0 ] = 1 dp[i][0] = 1 dp[i][0]=1 (t为空串, i–>[0…slen])
    • i = = 0 i==0 i==0时, d p [ 0 ] [ j ] = 0 dp[0][j] = 0 dp[0][j]=0(s为空串,j—>[1…tlen])
  • 状态转移:
    • s [ i − 1 ] ! = t [ j − 1 ] s[i-1] != t[j-1] s[i1]!=t[j1]时,有 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]
    • s [ i − 1 ] = = t [ j − 1 ] s[i-1] == t[j-1] s[i1]==t[j1]时,有 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]
class Solution {
public:
    int numDistinct(string s, string t) {
        int slen = s.size(), tlen = t.size();
        
        std::vector<std::vector<int>> dp(slen + 1, std::vector<int>(tlen + 1));


        for (int i = 0; i <= slen; ++i) {
            for (int j = 0; j <= tlen; ++j) {
                if(j == 0){
                    dp[i][j] = 1;
                }else if(i == 0){  // s只拿前0个字符做子序列, T前j个字符
                    dp[i][j] = 0;
                }else{
                    if(s[i - 1] == t[j - 1]){
                        dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                    }else{
                        dp[i][j] = dp[i - 1][j];
                    }
                }
            }
        }
        
        
        return dp[slen][tlen];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值