不同的子序列问题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. springboot中bean的重定义

    需求描述: 项目中应用其他项目的jar包,然后有些controller中的方法有缺陷需要修改. 1.配置添加 spring.main.allow-bean-definition-overriding= ...

  2. Python学习阵痛期

    Python和之前学习的Java语法上有较大的区别,例如Java中for循环常使用++自增符,在Python中是没有++的. 因为Python中整型.字符型等都是不可变的,一改变值就重新分配了新的内存 ...

  3. 2021.08.16 P1300 城市街道交通费系统(dfs)

    2021.08.16 P1300 城市街道交通费系统(dfs) P1300 城市街道交通费系统 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 城市街道交费系统最近创立了.一 ...

  4. MySQL 视图简介

    概述 数据库中关于数据的查询有时非常复杂,例如表连接.子查询等,这种查询编写难度大,很容易出错.另外,在具体操作表时,有时候要求只能操作部分字段. 为了提高复杂 SQL 语句的复用性和表的操作的安全性 ...

  5. Android 12(S) 图像显示系统 - 基础知识之 BitTube

    必读: Android 12(S) 图像显示系统 - 开篇 一.基本概念 在Android显示子系统中,我们会看到有使用BitTube来进行跨进程数据传递.BitTube的实现很简洁,就是一对&quo ...

  6. Spring Security实现基于RBAC的权限表达式动态访问控制

    昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制.我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式,所以这个东西对我不陌生,但是在Spring Securit ...

  7. 3.5 常用Linux命令

    1.touch命令 touch命令用于创建空白文件或设置文件的时间,语法格式为"touch [参数] 文件名称". 2.mkdir命令 mkdir命令用于创建空白的目录,英文全称为 ...

  8. Spring 源码(7)Spring的注解是如何解析的?

    上一篇 https://www.cnblogs.com/redwinter/p/16196359.html 介绍了BeanFactoryPostProcessor的执行过程,这篇文章介绍Spring中 ...

  9. vue-core-video-player-基于vue.js的视频播放器组件

    一 介绍 一款基于 vue.js 的轻量级的视频播放器插件插件 个性化配置 i18n 服务端渲染 画中画模式 事件订阅 易于开发 移动端适配 1.1 官方文档 https://core-player. ...

  10. esp8266 esp01s wifi继电器 初步点灯成功!艰难的历程啊,期间差点烧了

    0x00 前言说明 放假这几天,在淘宝买了esp01s,和一个搭配esp01s的wifi继电器准备做一些IOT(物联网)实验,踩了不少的坑,总算是点灯成功了!下面记录一些实验的拍照吧~ 继电器参数说明 ...