leetCode 115.不同的子序列 动态规划 + 滚动数组(优化)

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

示例 1:

输入:s = "rabbbit", t = "rabbit"
输出3
解释:如下所示, 有 3 种可以从 s 中得到 rabbit" 的方案
rabbbit
rabbbit
rabbbit

 示例 2:

输入:s = "babgbag", t = "bag"
输出5
解释:如下所示, 有 5 种可以从 s 中得到 "bag" 的方案 
babgbag
babgbag
babgbag
babgbag
babgbag

>>思路和分析

下文参考(~ ̄(OO) ̄)ブ笨猪爆破组的文章解法和文字:115. 不同的子序列 - 力扣(LeetCode)

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

(1)递归思路:抓住“选”要按照 t 来挑选,逐字符考察“选”“不选”,分别来到什么状态?

  • 1.s[i] == t[j]
    • 举例s babgbag,t bag,末尾字符相同,故 s 两种选择
    • 注意int n = s.length(),m = t.length();
      • 1.用s[n-1] 去匹配掉 t[m-1],问题规模缩小:继续考察 babgba ba 
      • 2.若s[n-1] 不去匹配掉 t[m-1],可由于t[m-1] 仍需被匹配,于是在 babgba 中继续挑,考察babgba bag
    • 是否用s[n-1]去匹配t[m-1]是两种不同的挑选方式,各自做下去所产生的方式数,相加起来,是大问题的解

  • 2.s[i] != t[j]
    • s[i] 不匹配 t[j],唯有拿 s[i] 之前的子串去匹配

(2)递归函数返回

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

(3)递归树底部的base case

一步步地递归压栈,子问题规模(子串长度)在变小:

  • 小到 t 变成空串,此时 s 去匹配它,方式只有一种:就是什么字符都不用挑(或 s 也是空串,啥也不用做也可匹配,方式数也是1)
  • 小到 s 变成空串,但t不是, s 是没有办法匹配 t 的,方式数为0

递归函数的参数可以传子串或索引:这里推荐用索引描述子问题,因为不用每次都切割字符串,也更容易迁移到dp解法去

一、递归搜索 (会超时)

  • 超出时间限制
class Solution {
public:// 递归搜索 (会超时)int numDistinct(string s,string t) {const int n = s.length(),m = t.length();function<int(int,int)> dfs = [&](int i,int j) -> int {if(j<0) return 1;// base caseif(i<0) return 0;// 这两个base case 的顺序不能调换!因为 i<0 且 j<0 时 应该返回1if(s[i] == t[j]) return dfs(i-1,j) + dfs(i-1,j-1);else return dfs(i-1,j);};return dfs(n-1,m-1);}
};

二、递归搜索 + 保存计算结果 = 记忆化搜索

  • 二维memo数组 存储计算过的子问题的结果 
// 递归搜索 + 保存计算结果 = 记忆化搜索int numDistinct(string s, string t) {int n = s.length(),m = t.length(),memo[n][m]; // 二维memo数组 存储计算过的子问题的结果memset(memo,-1,sizeof(memo));// -1 表示没有访问过function<int(int,int)> dfs = [&](int i,int j) -> int { // 从开头到s[i]的子串中,出现『从开头到t[j]的子串』的 次数if(j<0) // base case 当j指针越界,此时t为空串,s不管是不是空串,匹配方式数都是1return 1;if(i<0) // base case i指针越界,此时s为空串,t不是,s怎么也匹配不了t,方式数0return 0;if (memo[i][j] !=  -1) // memo中有当前遇到的子问题的解,直接拿来返回return memo[i][j];if (s[i] == t[j]) {  // t[j]被匹配掉,对应dfs(i-1, j-1),不被匹配掉对应dfs(i-1, j)memo[i][j] = dfs(i-1, j) + dfs(i-1, j-1);} else {memo[i][j] = dfs(i-1, j);}return memo[i][j];// 返回当前递归子问题的解};return dfs(n-1,m-1);//从开头到s[n-1]的子串中,出现『从开头到t[m-1]的子串』的次数}

也可以写成这样的代码: 

class Solution {
public: // 递归搜索 + 保存计算结果 = 记忆化搜索int numDistinct(string s, string t) {int n = s.length(),m = t.length(),memo[n][m]; memset(memo,-1,sizeof(memo));function<int(int,int)> dfs = [&](int i,int j) -> int { if(j<0) return 1;if(i<0) return 0;int &res = memo[i][j];if (res !=  -1) return res;if (s[i] == t[j]) return res = dfs(i-1, j) + dfs(i-1, j-1);return res = dfs(i-1, j);};return dfs(n-1,m-1);}
};

三、动态规划 与 递归 的区别 

  • 递归公式 
if (s[i] == t[j]) { memo[i][j] = dfs(i-1, j) + dfs(i-1, j-1);
} else {memo[i][j] = dfs(i-1, j);
}

递归是自上而下调用,子问题自下而上被解决,最后解决了整个问题,而dp是从base case 出发,通过在dp数组记录中间结果,自下而上地顺序地解决子问题

  • dp解法

1.确定dp数组(dp table)以及下标的含义

dp[i][j]:从开头到s[i-1]的子串中,出现『从开头到t[j-1]的子串』的 次数。即:

  • i 个字符的 s 子串中,出现前 j 个字符的 t 子串的次数
  • 或者说 以i-1为结尾的s子序列中出现以j-1为结尾的 t 的个数

2.确定递推公式

状态转移方程:

  • 当s[i-1] != t[j-1]时,有dp[i][j] = dp[i-1][j]
  • 当s[i-1] == t[j-1]时,有dp[i][j] = dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

3.dp数组初始化

base case

  • j==0时,dp[i][0] = 1
  • i==0时,dp[0][j] = 0

 

 也可从递推公式dp[i][j] = dp[i-1][j-1] + dp[i-1][j];dp[i][j] = dp[i-1][j];中可以看出dp[i][j]是从上方和左方推导而来的,故:dp[i][0] 和 dp[0][j] 是一定要初始化的

4.确定遍历顺序

从递推公式我们可以看出dp[i][j]是从上方和左方推导而来的,所以遍历的时候一定是从上到下、从左到右,可以保证dp[i][j]可以根据之前计算出来的数值进行计算

5.举例推导dp数组

以s:"heeeheheda",t:"heheda"为例,推导dp数组状态如下(可以参考此图,在不知道递推式的情况下找出规律,手推递推式)

以s:"babgbag",t:"bag"为例,推导dp数组状态如下:

以s:"rabbbit",t:"rabbit"为例,推导dp数组状态如下:

 (一)动态规划 二维dp

class Solution {
public:// 动态规划 二维dp数组int numDistinct(string s, string t) {int n = s.length(),m = t.length();uint64_t dp[n+1][m+1];memset(dp,0,sizeof(dp));for(int i=0;i<n;i++) dp[i][0] = 1;// for(int j=1;j<m;j++) dp[0][j] = 0;for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {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[n][m];}
};
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(n * m)

 (二)动态规划 二维dp 优化空间复杂度

class Solution {
public:// 动态规划 二维dp优化空间复杂度int numDistinct(string s, string t) {int n = s.length(),m = t.length();uint64_t dp[2][m+1];memset(dp,0,sizeof(dp));for(int i=0;i<2;i++) dp[i][0] = 1;for(int j=1;j<m;j++) dp[0][j] = 0;for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {if(s[i-1] == t[j-1]) dp[i%2][j] = dp[(i-1)%2][j-1] + dp[(i-1)%2][j];else dp[i%2][j] = dp[(i-1)%2][j];}}return dp[n%2][m];}
}
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(m)

 (三)动态规划 一维dp(滚动数组) 优化空间复杂度

class Solution {
public:// 动态规划 一维dp 优化空间复杂度int numDistinct(string s, string t) {int n = s.length(),m = t.length();uint64_t dp[m+1];memset(dp,0,sizeof(dp));dp[0] = 1;for(int j=1;j<m;j++) dp[j] = 0;for(int i=1;i<=n;i++) {// int pre = dp[0];// for(int j=1;j<=m;j++) {// int tmp = dp[j];// if(s[i-1] == t[j-1]) dp[j] = pre + dp[j];// else dp[j] = dp[j];// pre = tmp;// }for(int j=m;j>=1;j--) {if(s[i-1] == t[j-1]) dp[j] = dp[j-1] + dp[j];}}return dp[m];}
};
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(m)

 参考和推荐文章、视频:

115. 不同的子序列 - 力扣(LeetCode)

代码随想录 (programmercarl.com)

动态规划之子序列,为了编辑距离做铺垫 | LeetCode:115.不同的子序列_哔哩哔哩_bilibili 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/102997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于:未同意隐私政策,应用获取ANDROID ID问题2

一、环境 Unity2018 4.21f1、Android Studio、Windows10 二、问题描述 在发布应用到华为应用市场时&#xff0c;提示“在用户同意隐私政策前&#xff0c;您的应用获取了用户的ANDROID ID&#xff0c;不符合华为应用市场审核标准。” 如果你想去掉获取ANDROID ID的代码可以参…

Webmin远程命令执行漏洞复现报告

漏洞编号 CVE-2019-15107 漏洞描述 Webmin是一个基于Web的系统配置工具&#xff0c;用于类Unix系统。密码重置页面中存在此漏洞&#xff0c;允许未经身份验证的用户通过简单的 POST 请求执行任意命令。 影响版本 Webmin<1.920 漏洞评级 严重 利用方法&#xff08;利…

STM32MP157按键中断实验

按键配置 #include "key_it.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_gic.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_rcc.h"void key_it_config() {/* RCC使能GPIOF时钟 */RCC->MP_AHB4ENSE…

ubuntu20.04 nerf Instant-ngp (下) 复现,自建数据集,导出mesh

参考链接 Ubuntu20.04复现instant-ngp&#xff0c;自建数据集&#xff0c;导出mesh_XINYU W的博客-CSDN博客 GitHub - NVlabs/instant-ngp: Instant neural graphics primitives: lightning fast NeRF and more youtube上的一个博主自建数据集 https://www.youtube.com/watch…

es6(二)——常用es6说明

ES6的系列文章目录 es6&#xff08;一&#xff09;——var和let和const的区别 文章目录 ES6的系列文章目录一、变量的结构赋值1.数组的结构赋值2.对象的结构赋值 二、模板字符串三、扩展运算符1.字符串的使用2.数组的使用 四、箭头函数1.普通函数的定义2.箭头函数的定义3.箭头…

uniapp app获取keystore等一系列常用数据

https://blog.csdn.net/deepdfhy/article/details/88698492 参考文章 一、获取安卓证书keystore的SHA1和SHA256值 参数上面引用链接 window r : $ cmd $ D: 进入D盘 $ keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore 项目名称.ke…

[GXYCTF2019]BabyUpload - 文件上传+绕过(后缀文件类型文件内容.htaccess)

[GXYCTF2019]BabyUpload 解题流程 解题流程 1、上传一句话&#xff0c;提示“后缀不允许ph” 2、修改后缀为jpg&#xff0c;提示“上传类型也太露骨了吧&#xff01;” 3、修改类型为image/jpeg&#xff0c;提示“诶&#xff0c;别蒙我啊&#xff0c;这标志明显还是php啊” 4、…

【Linux 安装Kibana 及 Es 分词器安装】

一、客户端Kibana安装 Kibana是一个开源分析和可视化平台&#xff0c;旨在与Elasticsearch协同工作。参考文档 1. 下载并解压缩Kibana 下载路径 选择的版本是和 ElasticSearch 对应&#xff08;7.17.3&#xff09; 下载后上传到Linux 系统中&#xff0c;并放在 /root/ 下&a…

Linux编译FFmpeg

Linux编译FFmpeg 1. 下载FFmpeg源码 FFmpeg源码下载地址&#xff1a;http://ffmpeg.org/download.html 在下面选择版本 2. 解压并创建生成目录 tar xvf ffmpeg-snapshot.tar.bz2 // 解压下载的FFmpeg源码 makedir /root/ffmpeg // 创建生成目录3. 编译FFmpeg 进入FF…

Transformer模型 | 基于Spatial-Temporal Transformer的城市交通流预测

交通预测已成为智能交通系统的核心组成部分。然而,由于交通流的高度非线性特征和动态的时空依赖性,及时准确的交通预测,尤其是长时交通流预测仍然是一个开放性的挑战。在这篇文章中,作者提出了一种新的时空Transformer网络(STTNs)模型,该模型联合利用了动态有向的空间依…

centos安装redis教程

centos安装redis教程 安装的版本为centos7.9下的redis3.2.100版本 1.下载地址 Index of /releases/ 使用xftp将redis传上去。 2.解压 tar -zxvf 文件名.tar.gz 3.安装 首先&#xff0c;确保系统已经安装了GCC编译器和make工具。可以使用以下命令进行安装&#xff1a; sudo y…

配置XP虚拟机和Win 10宿主机互相ping通

文章目录 一、关闭虚机和宿主机的防火墙1、关闭虚拟机的防火墙1.1方式一1.2方式二 2、关闭宿主机的防火墙 二、设置XP和宿主机VMnet8的IP地址、网关和DNS1、获取VMWare的虚拟网络配置信息2、设置XP的VMnet8的IP地址、网关和DNS3、设置宿主机VMnet8的IP地址、网关和DNS 三、获取…

一款新的webshell管理工具

Alien 项目简介 语言 C# .NET Framework V4.8 功能 File Manager &#xff08;可显示图片&#xff0c; 可SearchFile&#xff09; 虚拟终端 数据库 注册表 监控 截图 系统信息 项目描述 一句话木马 一句话木马是在渗透测试中用来控制服务器的工具 强大之处在于木…

使用telnet+nc工具测试网络连通性

背景&#xff1a; 正常情况下使用ping命令即可测试网络的连通性&#xff0c;但如果做了内网穿透(端口转发)&#xff0c;则需要指定网络端口&#xff0c;此时ping命令无法实现ipport的连通性测试。则可以使用telnetnc测试网络连通性。 环境&#xff1a; 两台服务器都是按照的De…

[Machine learning][Part4] 多维矩阵下的梯度下降线性预测模型的实现

目录 模型初始化信息&#xff1a; 模型实现&#xff1a; 多变量损失函数&#xff1a; 多变量梯度下降实现&#xff1a; 多变量梯度实现&#xff1a; 多变量梯度下降实现&#xff1a; 之前部分实现的梯度下降线性预测模型中的training example只有一个特征属性&#xff1a…

hive3.1核心源码思路

系列文章目录 大数据主要组件核心源码解析 文章目录 系列文章目录大数据主要组件核心源码解析 前言一、HQL转化为MR 核心思路二、核心代码1. 入口类&#xff0c;生命线2. 编译代码3. 执行代码 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 对大…

LeetCode-144-二叉树的前序遍历

题目描述&#xff1a; 题目链接&#xff1a;LeetCode-144-二叉树的前序遍历 递归法 解题思路&#xff1a; 方法一&#xff1a;递归。 要先清楚前序遍历的顺序&#xff1a;先根节点&#xff0c;再左子树&#xff0c;再右子树。 然后是递归三部曲&#xff1a; 确定递归函数的参数…

C# .net创建一个MVC框架工程

二、C# .net创建一个MVC框架工程 1.步骤 首先打开VS &#xff0c;然后点击创建新项目 在三个选项框中输入我们需要的项目条件 最后一步创建完毕 创建会在资源解决方案生成如图&#xff1a;

FreeRTOS学习笔记——四、任务的定义与任务切换的实现

FreeRTOS学习笔记——四、任务的定义与任务切换的实现 0 前言1 什么是任务2 创建任务2.1 定义任务栈2.2 定义任务函数2.3 定义任务控制块2.4 实现任务创建函数2.4.1 任务创建函数 —— xTaskCreateStatic()函数2.4.2 创建新任务——prvInitialiseNewTask()函数2.4.3 初始化任务…

基于Cucumber的行为驱动开发(BDD)实例

本篇介绍 Cucumber 的基本使用&#xff0c; 因为Cucumber是BDD的工具&#xff0c; 所以首先需要弄清楚什么是BDD&#xff0c;而在介绍BDD之前&#xff0c;先看看常见的软件开发方法。 常见的软件开发方法 面向过程开发&#xff08;Procedural Development&#xff09;&#x…