代码随想录算法训练营第五十四天丨 动态规划part15

news2024/11/26 14:25:58

392.判断子序列

思路

(这道题也可以用双指针的思路来实现,时间复杂度也是O(n))

这道题应该算是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。

所以掌握本题的动态规划解法是对后面要讲解的编辑距离的题目打下基础

动态规划五部曲分析如下:

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

dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

注意这里是判断s是否为t的子序列。即t的长度是大于等于s的。

为啥要表示下标i-1为结尾的字符串呢,为啥不表示下标i为结尾的字符串呢?

为什么这么定义卡哥在 718. 最长重复子数组 (opens new window)中做了详细的讲解。

其实用i来表示也可以!

但我统一以下标i-1为结尾的字符串来计算,这样在下面的递归公式中会容易理解一些,如果还有疑惑,可以继续往下看。

  • 确定递推公式

在确定递推公式的时候,首先要考虑如下两种操作,整理如下:

  • if (s[i - 1] == t[j - 1])
    • t中找到了一个字符在s中也出现了
  • if (s[i - 1] != t[j - 1])
    • 相当于t要删除元素,继续匹配

if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1(如果不理解,在回看一下dp[i][j]的定义

if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];

其实这里 大家可以发现和 1143.最长公共子序列 (opens new window)的递推公式基本那就是一样的,区别就是 本题 如果删元素一定是字符串t,而 1143.最长公共子序列 是两个字符串都可以删元素。

  • dp数组如何初始化

从递推公式可以看出dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],所以dp[0][0]和dp[i][0]是一定要初始化的。

这里大家已经可以发现,在定义dp[i][j]含义的时候为什么要表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

因为这样的定义在dp二维矩阵中可以留出初始化的区间,如图:

392.判断子序列

如果要是定义的dp[i][j]是以下标i为结尾的字符串s和以下标j为结尾的字符串t,初始化就比较麻烦了。

dp[i][0] 表示以下标i-1为结尾的字符串,与空字符串的相同子序列长度,所以为0. dp[0][j]同理。

int[][] dp = new int[t.length()+1][s.length()+1];
  • 确定遍历顺序

同理从递推公式可以看出dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],那么遍历顺序也应该是从上到下,从左到右

如图所示:

392.判断子序列1

  • 举例推导dp数组

以示例一为例,输入:s = "abc", t = "ahbgdc",dp状态转移图如下:

392.判断子序列2

dp[i][j]表示以下标i-1为结尾的字符串s和以下标j-1为结尾的字符串t 相同子序列的长度,所以如果dp[s.size()][t.size()] 与 字符串s的长度相同说明:s与t的最长相同子序列就是s,那么s 就是 t 的子序列。

图中dp[s.size()][t.size()] = 3, 而s.size() 也为3。所以s是t 的子序列,返回true。

动规五部曲分析完毕,C++代码如下:

class Solution {
    public boolean isSubsequence(String s, String t) {
        int[][] dp = new int[t.length()+1][s.length()+1];
        for (int i = 1; i <= t.length(); i++) {

            for (int j = 1; j <= s.length(); j++) {
                if (s.charAt(j-1) == t.charAt(i-1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else {
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[t.length()][s.length()] == s.length() ? true : false;
    }
}
  • 时间复杂度:O(n × m)
  • 空间复杂度:O(n × m)

115.不同的子序列

思路

这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用KMP。

这道题目相对于72. 编辑距离,简单了不少,因为本题相当于只有删除操作,不用考虑替换增加之类的。

但相对于刚讲过的动态规划:392.判断子序列 (opens new window)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下:

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

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。

为什么i-1,j-1 这么定义卡哥在 718. 最长重复子数组 (opens new window)中做了详细的讲解。

  • 确定递推公式

这一类问题,基本是要分析两种情况

  • s[i - 1] 与 t[j - 1]相等
  • s[i - 1] 与 t[j - 1] 不相等

当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。

一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]。

一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。

这里可能有录友不明白了,为什么还要考虑 不用s[i - 1]来匹配,都相同了指定要匹配啊

例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。

当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。

所以当s[i - 1] 与 t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]

所以递推公式为:dp[i][j] = dp[i - 1][j];

这里可能有录友还疑惑,为什么只考虑 “不用s[i - 1]来匹配” 这种情况, 不考虑 “不用t[j - 1]来匹配” 的情况呢。

这里大家要明确,我们求的是 s 中有多少个 t,而不是 求t中有多少个s,所以只考虑 s中删除元素的情况,即 不用s[i - 1]来匹配 的情况。

  • dp数组如何初始化

从递推公式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]是一定要初始化的。

每次当初始化的时候,都要回顾一下dp[i][j]的定义,不要凭感觉初始化。

dp[i][0]表示什么呢?

dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。

那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。

再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。

那么dp[0][j]一定都是0,s如论如何也变成不了t。

最后就要看一个特殊位置了,即:dp[0][0] 应该是多少。

dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。

初始化分析完毕,代码如下:

int[][] dp = new int[s.length() + 1][t.length() + 1];
for (int i = 0; i < s.length(); i++) {
    dp[i][0] = 1;
}
  • 确定遍历顺序

从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j]都是根据左上方和正上方推出来的。

所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

代码如下:

 for (int i = 1; i <= s.length(); i++) {
     for (int j = 1; j <= t.length(); j++) {
         if (s.charAt(i - 1) == t.charAt(j - 1)) {
             dp[i][j] = dp[i - 1][j - 1] + dp[i-1][j];
         }else {
             dp[i][j] = dp[i-1][j];
         }
     }
 }
  • 举例推导dp数组

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

115.不同的子序列

如果写出来的代码怎么改都通过不了,不妨把dp数组打印出来,看一看,是不是这样的。

动规五部曲分析完毕,代码如下:

class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];

        for (int i = 0; i < s.length(); i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 1; j <= t.length(); j++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i-1][j];
                }else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[s.length()][t.length()];
    }
}
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(n * m)

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

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

相关文章

【问题处理】WPS提示不能启动此对象的源应用程序如何处理?

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 最近在用WPS打开word文件中&#xff0c;插入的Excel附件时&#xff0c;无法打开&#xff0c;提示&#xff1a;“不能启动此对象的源应用程序”。 经过上网查找处理办法&#xff0c;尝试解决&#xff0c;现将解决过程记…

MIB 6.1810实验Xv6 and Unix utilities(2)sleep

难度:easy Implement a user-level sleep program for xv6, along the lines of the UNIX sleep command. Your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts f…

leetcode:移除链表元素

1.题目描述 题目链接&#xff1a;203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 2.解题思路 我们定义一个cur指向当前结点&#xff0c;定义prev指向前一个结点&#xff0c;next指向下一个结点 如果cur->valval&#xff0c;那我们就删除这个结点 怎么删除呢…

如何在聊天记录中实时查找大量的微信群二维码

10-5 如果你有需要从微信里收到的大量信息中实时找到别人发到群里的二维码&#xff0c;那本文非常适合你阅读&#xff0c;因为本文的教程&#xff0c;可以让你在海量的微信消息中&#xff0c;实时地把二维码自动挑出来&#xff0c;并且帮你分类保存。 如果你是做网推的&#…

桂院校园导航 静态项目 二次开发教程 1.3

Gitee代码仓库&#xff1a;桂院校园导航小程序 GitHub代码仓库&#xff1a;GLU-Campus-Guide 升级日志 1.3 优化了小程序的数据存储方式&#xff0c;对部分页面进行了调整&#xff0c;调整了功能和代码。 引入weui组件库&#xff0c;使地点的呈现方式更加美观 序号内容详情…

例解什么是Python装饰器

Python中的装饰器一直是一个比较难理解的概念&#xff0c;我自己理解的就是用一个函数去修改另一个函数&#xff0c;主要是为另一个函数添加计时等功能&#xff0c;而且不用改变另一个函数&#xff0c;这样就大大减少了另一个函数的维护成本。 这个装饰器&#xff0c;英文名就…

前端404页面的制作

1、背景 前端开发经常遇到输入路径不存在的问题&#xff0c;为此&#xff0c;把之前项目的404拿出来供大家参考。代码很简单&#xff0c;适合新手入手&#xff0c;效果如下&#xff1a; 2、代码引用的是element-plus框架 <template><div><el-result icon"…

jQuery【事件处理器、鼠标事件、表单事件、键盘事件、浏览器事件、事件对象、jQuery遍历】(三)-全面详解(学习总结---从入门到深化)

目录 事件之绑定事件处理器 事件之鼠标事件 事件之表单事件 事件之键盘事件 事件之浏览器事件 事件对象 jQuery遍历 事件之绑定事件处理器 1、 .on() 在选定的元素上绑定一个或多个事件处理函数 $("#button").on("click", function(event){console…

笔记53:torch.nn.rnn() 函数详解

参数解释&#xff1a; &#xff08;1&#xff09;input_size()&#xff1a;即输入信息 Xt 的每个序列的独热编码向量的长度&#xff0c;即 len(vocab) &#xff08;2&#xff09;hidden_size()&#xff1a;即隐变量 h 的维度&#xff08;维度是多少&#xff0c;就代表用几个数…

【JUC】九、线程池ThreadPool

文章目录 1、线程池2、分类3、线程池的使用4、工作流程5、拒绝策略6、线程池的七个参数7、自定义线程池8、什么时候考虑使用线程池&#xff1f; 1、线程池 线程池和数据库连接池的理念很相似&#xff0c;对于数据库连接池&#xff1a;普通的连接数据库是建立一个JDBC连接&…

本地开发环境和服务器传输数据的几种方法

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

注册表单mvc

jsp给我们的ControllerServlet 1在哪看到我们的数据呢 2什么时候用了session,有什么用 register.jsp 获取表单的name,email formBean.name是怎么定义的 3为什么就可以formbean访问 要使用的jsp对象都在servlet里面用setAttribute定义的 request.getSession().setAttribute…

听GPT 讲Rust源代码--library/core/src(8)

题图来自 Hello, crustaceans. File: rust/library/core/src/future/ready.rs 在Rust源代码中&#xff0c;rust/library/core/src/future/ready.rs文件的作用是定义了一个名为Ready的Future类型。Ready是一个简单的Future实现&#xff0c;它立即返回一个给定的值。 Ready 是一个…

大数据Doris(二十四):数据导入(Stream Load)介绍

文章目录 数据导入(Stream Load)介绍 一、适用场景 二、基本原理

quartz笔记

Quartz-CSDN博客 上面是Quartz的一些基本知识,如果对quartz的基本API不是很了解的话,建议先看下上面的 和Linux Crontab对比 1.执行粒度: Linux Crontab是进程级 quart是线程级 2.跨平台性: Crontab只能在Linxu运行 quart是java实现,可以跨平台 3.调度集上 Crontab的…

网络的概念与定义

一.网络的概念与定义 1.1 网络的概念 具有独立功能的计算机通过通信介质连接起来就形成了网络。为了满足人们的各种需求&#xff0c;比如访问网页&#xff0c;在线游戏&#xff0c;在线视频等&#xff0c;会形成比如文本&#xff0c;图片&#xff0c;视频等都是信息的不同呈现方…

【AI视野·今日NLP 自然语言处理论文速览 第六十四期】Fri, 27 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Fri, 27 Oct 2023 Totally 80 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers torchdistill Meets Hugging Face Libraries for Reproducible, Coding-Free Deep Learning Studies: A Case …

11.16 知识总结(模型层更多内容)

一、 多表查询&#xff08;跨表查询&#xff09; <br class"Apple-interchange-newline"><div></div> 子查询&#xff1a;分步查询 链表查询&#xff1a;把多个有关系的表拼接成一个大表(虚拟表) inner join left join right join 1.1 基于双下划…

【EI会议征稿】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#…

鸿蒙4.0真机调试踩坑

传言鸿蒙next版本将不再兼容Android&#xff0c;所以领导安排做下鸿蒙开发的调研工作。 鸿蒙开发指南其实已经非常的友好了。但是鸿蒙开发本身还是有些坑要踩&#xff0c;这篇文章主要讲了鸿蒙真机调试问题。 目前手上的真机为华为 nova6&#xff0c;处理器为麒麟990.鸿蒙系统…