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

news2024/11/18 16:22:24

给你两个字符串 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 case
            if(i<0) return 0;// 这两个base case 的顺序不能调换!因为 i<0 且 j<0 时 应该返回1
            if(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不管是不是空串,匹配方式数都是1
                return 1;
            if(i<0) // base case i指针越界,此时s为空串,t不是,s怎么也匹配不了t,方式数0
                return 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.coloradmin.cn/o/1082076.html

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

相关文章

VS2017+QT+PCL环境配置

1、前置知识 C++ Qt VTK/OPenGL喧杂C 2、环境搭建 1、visual studio 2017安装,从官网上下载对应安装程序。选择C++安装即可 2、Qt安装若是没有账号和密码,选择dont have qt accout? Sign up 根据自己的项目需要选择安装那些组件 2、PCL安装 下载地址:https://github.co…

matlab绘制尖角colorbar

Matlab代码 cmap [69 117 180116 173 203171 217 233254 224 144253 174 77244 109 67215 48 39165 0 38]/255; %画图的部分代码 figure set(gcf,outerposition,get(0,screensize)) ax axes(Position,[0.2 0.2 0.6 0.6]); % pos需要自己设置位置 h colorbar; % colormap(ax…

黑客技术(网络安全)——自学思路

如果你想自学网络安全&#xff0c;首先你必须了解什么是网络安全&#xff01;&#xff0c;什么是黑客&#xff01;&#xff01; 1.无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面性&#xff0c;例如 Web 安全技术&#xff0c;既有 Web 渗透2.也有 Web 防…

【docker】资源使用率验证及告警

说明 Docker资源使用率可以通过以下命令进行验证&#xff1a; 使用 docker stats 命令查看容器的实时资源使用情况&#xff0c;例如&#xff1a; docker stats <container_id> 该命令会展示容器的CPU、内存、网络IO、磁盘IO等各项资源的使用情况。2. 使用 docker sta…

在QGIS中给矢量数据属性编号的一种方法

目录 写在文章前 一、给要素编号用哪些功能 二、实现一个最简单的编号 1.数据准备 2.编辑字段计算器表达式 3.查看编号结果 三、更加复杂的编号 1.使用UUID编号 2.根据单个属性排序后编号 3.根据多个属性排序后编号 4.拼接字符串进行编号 5.根据时间编号及实现 写在…

快速学习微服务保护框架--Sentinel

学习一个框架最好的方式就是查看官方地址,sentinel是国内阿里巴巴公司的,官网更方便官网 官网 微服务保护框架 Sentinel 1.初识Sentinel 1.1.雪崩问题及解决方案 1.1.1.雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微…

VScode使用SSH去编辑远程文件

Visual Studio Code (VS Code) 是一款强大的文本编辑器&#xff0c;它支持使用 SSH 连接远程服务器进行开发。通过 SSH 连接远程服务器&#xff0c;您可以在本地编写程序&#xff0c;并将代码上传到远程服务器执行。 安装插件 需要安装这两个插件 安装完成之后&#xff0c;右…

ROS-PX4仿真笔记_1

offbord模式测试 rosrun offboard_pkg position stablelize模式 lqr控制器实验 roslaunch px4 fast_test.launch 无人机起飞1.5-2m sh mybot_gazebo.sh先点击mode&#xff0c;再点击cmd&#xff0c;才能打开offbord模式 minijerk实验 roslaunch px4 fast_test.launch sh …

KBU1510-ASEMI开关电源整流桥KBU1510

编辑&#xff1a;ll KBU1510-ASEMI开关电源整流桥KBU1510 型号&#xff1a;KBU1510 品牌&#xff1a;ASEMI 芯片个数&#xff1a;4 封装&#xff1a;KBU-4 恢复时间&#xff1a;&#xff1e;50ns 工作温度&#xff1a;-55C~150C 浪涌电流&#xff1a;200A 正向电流&am…

c++ 学习之 强制类型转换运算符 const_cast

看例子怎么用 int main() {int a 1;int* p a;// 会发生报错// 如果学着 c的风格类型转换int* pp (int*)a;*pp 1; // 编译不报错&#xff0c;但是运行报错// const_castconst int n 5;const std::string s "lalal";// const cast 只针对指针&#xff0c;引用&…

HTTPS 加密全过程

加密协议以前是SSL,现在都是TLS, 而证书现在大多数都是SSL证书 抓包流程: TCP三次握手过后, 客户端发送Client Hello 服务器相应Server Hello 服务器再次响应发送证书: 服务器再发送公钥:

Java线程池原理解析

目录 一、为什么引入线程池技术&#xff1f;二、Executor框架2.1 Runnable、Callable与Future接口2.2 Executor接口2.2.1 Executor2.2.2 ExecutorService 三、Java中线程池的工作原理3.1 ThreadPoolExecutor中核心的变量及常量3.2 线程池的任务调度逻辑3.2.1 addWorker方法3.2.…

Pycharm远程debug代码,一直进入remote_sources

最近debug发现代码一直跳转到 AppData\Local\JetBrains\PyCharm2022.2\remote_sources\xxx这样的目录下&#xff0c;查找百度也没有找到解决的方法。 最后发现&#xff0c;在Run的配置这&#xff0c;有一个Path mappings是空的&#xff0c;把这里的映射填成本地项目和远程项目…

Springboot+vue4S店车辆管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue4S店车辆管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的4S店车辆管理系统&#xff0c;采用M&#xff08…

Java子类继承父类私有方法属性问题讲解

Java子类继承父类私有方法属性问题讲解 结论一、案例准备二、测试方法&#xff1a;使用反射获取子类私有方法和私有属性具体操作&#xff08;获取私有方法&#xff09;具体操作&#xff08;获取私有属性&#xff09; 结论 Java 父类私有方法和私有属性不能被子类继承。 一、案…

IDEA 2023.2.2图文安装教程及下载

IDE 系列的第二个年度更新现已发布&#xff0c;涵盖 IntelliJ IDEA、WebStorm、PyCharm、DataGrip、GoLand、DataSpell 以及 All Products Pack 订阅中包含的其他工具。该版本还包括多项用户体验增强功能&#xff0c;例如 Search Everywhere&#xff08;随处搜索&#xff09;中…

Layer 2:百倍利润无限可能的首选赛道

截至 2023 年 9 月&#xff0c;以太坊在过去一年中上涨了 22%&#xff0c;表现优于大多数大型智能合约区块链。第 2 层 (L2) 在推动以太坊价值方面发挥了重要作用。 Layer 2 可以增强以太坊的可扩展性&#xff0c;使用户的网络成本降低 100 倍。 Coinbase 于 8 月推出了 BASE…

彻底弄懂Java中的MultipartFile接口和File类

前言 不管是在项目中还是日常需求&#xff0c;我们总是有操作文件数据的需求&#xff0c;Java中操作文件不可避免就要使用File类&#xff0c;而Spring中为我们提供了一个操作文件的接口&#xff0c;通过该接口我们可以获取用户上传的文件对象并写入文件系统中。 文章目录 前言…

【基础篇】二、Flink的批处理和流处理API

文章目录 0、demo模块创建1、批处理有界流2、流处理有界流3、流处理无界流4、The generic type parameters of Collector are missing 0、demo模块创建 创建个纯Maven工程来做演示&#xff0c;引入Flink的依赖&#xff1a;&#xff08;注意不同本版需要导入的依赖不一样&#…

【ComfyUI】MacBook Pro 安装(Intel 集成显卡)

文章目录 环境概述配置pip镜像配置pip代理git配置&#xff08;选配&#xff09;下载comfyUI代码创建、激活虚拟环境下载依赖安装torchvision启动comfyUI为什么Mac不支持CUDA&#xff0c;即英伟达的显卡&#xff1f;安装Intel工具包 环境 显卡&#xff1a;Intel Iris Plus Grap…