不同的子序列问题I

作者:Grey

原文地址: 不同的子序列问题I

题目链接

LeetCode 115. 不同的子序列

暴力解法

定义递归函数

int process(char[] str, char[] t, int i, int j)

递归函数表示:stri一直到最后,生成的序列可以匹配多少个t从j往后生成的字符串

所以process(str,t,0,0)得到的结果就是答案。

接下来考虑递归函数的base case

        if (j == t.length) {
// 表示str已经把t整个都搞定了,返回1,说明得到了一种情况
return 1;
}
// 到了这里,说明t还没到头
if (i == str.length) {
// str已经没有字符串了,t又没到头,所以,无法匹配
return 0;
}

接下来是普遍位置,考虑str[i]是否参与匹配来决定下一步的操作,注:str[i]如果要参与匹配,则必须满足str[i] == t[j]

        // str[i]位置不参与匹配
int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
// str[i]参与,必须满足str[i] == t[j]
ans += process(str, t, i + 1, j + 1);
}

完整代码如下

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
return process(s.toCharArray(), t.toCharArray(), 0, 0);
} // str[0....结尾]搞定t[0....结尾]
public static int process(char[] str, char[] t, int i, int j) {
if (j == t.length) {
// 全部搞定了
return 1;
}
if (i == str.length) {
// 没有了,搞不定
return 0;
}
// 不用i位置的去搞定
int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
ans += process(str, t, i + 1, j + 1);
}
return ans;
}

这个暴力解法在LeetCode上直接超时。

动态规划

二维数组

根据暴力方法,可以得到,递归函数只有两个可变参数,所以定义二维dpdp的含义和递归函数的含义保持一致。所以dp[0][0]就是答案。

        int m = str.length;
int n = target.length;
int[][] dp = new int[m + 1][n + 1];

根据暴力方法

        if (j == t.length) {
// 全部搞定了
return 1;
}
if (i == str.length) {
// 没有了,搞不定
return 0;
}

可以得到dp的最后一行都是1,即

        for (int i = 0; i < m + 1; i++) {
dp[i][n] = 1;
}

接下来考虑普遍的dp[i][j],根据暴力方法

        int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
ans += process(str, t, i + 1, j + 1);
}

可以得到,dp[i][j]依赖dp[i+1][j]dp[i+1][j+1](需要满足str[i] == t[j])位置的值。

所以

        for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
dp[i][j] = dp[i + 1][j] + (str[i] == target[j] ? dp[i + 1][j + 1] : 0);
}
}

完整代码

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
char[] str = s.toCharArray();
char[] target = t.toCharArray();
int m = str.length;
int n = target.length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < m + 1; i++) {
dp[i][n] = 1;
}
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
dp[i][j] = dp[i + 1][j] + (str[i] == target[j] ? dp[i + 1][j + 1] : 0);
}
}
return dp[0][0];
}

时间复杂度O(m*n),其中mn分别是st的长度。

空间复杂度O(m*n),其中mn分别是st的长度。

一维数组

通过分析上述动态规划的解法,我们可得到一个结论,二维dp的计算顺序是从最后一行到第一行,且当前行只依赖上一行有限几个位置的信息,所以,我们可以将上述二维表简化成一维表,定义

        int m = str.length;
int[] dp = new int[n + 1];

通过一维表的从最后一行到第一行的滚动更新,来得到第一行的值,完整代码如下

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
char[] str = s.toCharArray();
char[] target = t.toCharArray();
int m = str.length;
int n = target.length;
int[] dp = new int[n + 1];
dp[n] = 1;
for (int i = m - 1; i >= 0; i--) {
// 这里要注意,从左往右
for (int j = 0; j <= n - 1; j++) {
dp[j] += (str[i] == target[j] ? dp[j + 1] : 0);
} }
return dp[0];
}

时间复杂度O(m*n),其中mn分别是st的长度。

空间复杂度O(n),其中nt的长度。

更多

算法和数据结构笔记

不同的子序列问题I的更多相关文章

  1. 用python实现最长公共子序列算法(找到所有最长公共子串)

    软件安全的一个小实验,正好复习一下LCS的写法. 实现LCS的算法和算法导论上的方式基本一致,都是先建好两个表,一个存储在(i,j)处当前最长公共子序列长度,另一个存储在(i,j)处的回溯方向. 相对 ...

  2. codevs 1576 最长上升子序列的线段树优化

    题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...

  3. [LeetCode] Arithmetic Slices II - Subsequence 算数切片之二 - 子序列

    A sequence of numbers is called arithmetic if it consists of at least three elements and if the diff ...

  4. [LeetCode] Is Subsequence 是子序列

    Given a string s and a string t, check if s is subsequence of t. You may assume that there is only l ...

  5. [LeetCode] Wiggle Subsequence 摆动子序列

    A sequence of numbers is called a wiggle sequence if the differences between successive numbers stri ...

  6. [LeetCode] Increasing Triplet Subsequence 递增的三元子序列

    Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the ar ...

  7. [LeetCode] Distinct Subsequences 不同的子序列

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  8. 动态规划之最长公共子序列(LCS)

    转自:http://segmentfault.com/blog/exploring/ LCS 问题描述 定义: 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则 ...

  9. [Data Structure] LCSs——最长公共子序列和最长公共子串

    1. 什么是 LCSs? 什么是 LCSs? 好多博友看到这几个字母可能比较困惑,因为这是我自己对两个常见问题的统称,它们分别为最长公共子序列问题(Longest-Common-Subsequence ...

  10. 51nod1134(最长递增子序列)

    题目链接: https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1134 题意: 中文题诶~ 思路: 直接暴力的话时间复杂度为 ...

随机推荐

  1. Linux Red Hat 8.0虚拟机安装过程

    Linux Red Hat 8.0虚拟机安装过程 一.安装虚拟机所需要的工具: 1.VMware Workstation Pro 2.Red Hat 8.0 虚拟机镜像 3.还有一个就是VMware的 ...

  2. 轻量迅捷时代,Vite 与Webpack 谁赢谁输

    你知道Vite和Webpack吗?也许有不少"程序猿"对它们十分熟悉. Webpack Webpack是一个JavaScript应用程序的静态模块打包工具,它会对整个应用程序进行依 ...

  3. 2021.08.09 P4868 Preprefix sum(树状数组)

    2021.08.09 P4868 Preprefix sum(树状数组) P4868 Preprefix sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 前缀和(pr ...

  4. mmdetection 批量执行测试脚本

    在终端执行该脚本,传入所有的测试路径,每一个model的结果文件夹里面有一个best文件夹存放着其训练时最高mAP对应的权重,名字为best.pth dir=$(ls -l $1 |awk '/^d/ ...

  5. GraphScope v0.12.0 版本发布

    GraphScope 每月进行常规版本的迭代与发布,GraphScope v0.12.0 全新版本在四月如期而至.v0.12.0 为交互式图查询 GAIA 引入全新的 IR 层以及新增 Giraph ...

  6. golang bufio解析

    golang bufio 当进行频繁地对少量数据读写时会占用IO,造成性能问题.golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能. 在Transport中可 ...

  7. python学习-Day19

    目录 今日内容详细 正则表达式 开端 概念及相关符号 正则表达式之字符组 正则表达式之特殊符号 正则表达式之量词 小试牛刀 实现手机号码校验功能 复杂正则的编写 校验用户身份证号码 校验邮箱.快递单号 ...

  8. 5 分钟教你快速掌握 GitHub Actions 自动部署博客

    自从 GitHub 宣布 GitHub Actions 在平台上对所有开发人员和存储库可用以来,GitHub Actions 越来越受欢迎.很多第三方平台在生态系统中有速度等限制,将进一步推动开发人员 ...

  9. FreeRTOS --(16)资源管理之临界区

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107387427 临界区的概念在任何的 SoC 都存在,比如,针对一个寄存器,基本操作为 ...

  10. C# 有关List<T>的Contains与Equals方法

    [以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!]   !!!观前提醒!!! [本文内容可能较为复杂,虽然我已经以较为清晰的方式展 ...