代码随想录-刷题第五十五天

72. 编辑距离

题目链接:72. 编辑距离

思路:本题是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙地算出最少编辑距离。动态规划五步曲分析:

  1. dp[i][j]表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

  2. 递推公式:分情况讨论

    if (word1[i - 1] == word2[j - 1]),那么说明不用任何编辑,即dp[i][j] = dp[i - 1][j - 1]

    if (word1[i - 1] != word2[j - 1]),需要做增删或者替换操作:

    操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离再加上一个操作。即dp[i][j] = dp[i - 1][j] + 1

    操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离再加上一个操作。即dp[i][j] = dp[i][j - 1] + 1

    怎么都是删除元素,添加元素去哪了?

    word2添加一个元素,相当于word1删除一个元素,例如 word1 = "ad" ,word2 = "a"word1删除元素'd'word2添加一个元素'd',变成word1="a", word2="ad", 最终的操作数是一样!

    操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素。可以回顾一下,if (word1[i - 1] == word2[j - 1])的时候我们的操作是 dp[i][j] = dp[i - 1][j - 1]。那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。即dp[i][j] = dp[i - 1][j - 1] + 1

    因为是求最小编辑距离,所以取三种操作的最小值,

    dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1

  3. 初始化:dp[i][0]代表当word2为空串式,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显,dp[i][0] = i,同理dp[0][j] = j。

  4. 遍历顺序:

    由递推公式可以看出dp[i][j]是依赖左方、上方和左上方元素的,如图所示:

    72.编辑距离

    所以在dp矩阵中一定是从左到右、从上到下去遍历。

  5. 举例推导dp数组

    以输入:word1 = "horse", word2 = "ros"为例,推导dp数组状态图如下:

    72.编辑距离1

class Solution {
    public int minDistance(String word1, String word2) {
        char[] arr1 = word1.toCharArray();
        char[] arr2 = word2.toCharArray();
        int[][] dp = new int[arr1.length + 1][arr2.length + 1];

        // 初始化
        for (int i = 0; i <= arr1.length; i++) dp[i][0] = i;
        for (int j = 0; j <= arr2.length; j++) dp[0][j] = j;
        
        for (int i = 1; i <= arr1.length; i++) {
            for (int j = 1; j <= arr2.length; j++) {
                if (arr1[i - 1] == arr2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1],
                                        Math.min(dp[i - 1][j],
                                                 dp[i][j - 1])) + 1;
                }
            }
        }
        
        return dp[arr1.length][arr2.length];
    }
}

编辑距离总结

从判断子序列开始,都是编辑距离的问题,前三篇动态规划的文章就一直为编辑距离这道题目做铺垫。


647. 回文子串

题目链接:647. 回文子串

思路:动态规划五步曲分析:

  1. dp[i] 和 dp[i-1],dp[i + 1] 看上去都没啥关系,所以要看回文串的性质,如图:

    img

    在判断字符串s是否为回文,如果知道 s[1],s[2],s[3] 这个子串是回文的,那么只需要比较 s[0]和s[4]这两个元素是否相同,如果相同的话,这个字符串s就是回文串。

    此时是不是能找到一种递归关系,也就是判断一个字符串(字符串的下标范围[i, j])是否为回文,依赖于子字符串(下表范围[i + 1, j - 1])) 是否为回文。

    所以为了明确这种递归关系,dp数组是要定义成一个二维dp数组。

    布尔类型的dp[i][j]:表示区间范围[i, j](注意是左闭右闭)的子串是否为回文子串,如果是,dp[i][j]为true,否则为false。

  2. 递推公式:分情况讨论

    当s[i]与s[j]不相等,dp[i][j]一定是false

    当s[i]与s[j]相等时,有如下三种情况:

    情况一:下标 i 与 j 相同,同一个字符,例如a,当然是回文子串

    情况二:下标 i 与 j 相差为1,例如aa,也是回文子串

    情况三:下标 i 与 j 相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,看 i 到 j 区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i + 1 与 j - 1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。

  3. 初始化:dp[i][j]初始化为false。

    dp[i][j]可以初始化为true么? 当然不行,怎么能刚开始就全都匹配上了。

  4. 遍历顺序:

    从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,再对dp[i][j]进行赋值true的。dp[i + 1][j - 1]在dp[i][j]的左下角,如图:

    647.回文子串

    如果是从上到下、从左到右遍历,那么会用到没有计算过的dp[i + 1][j - 1],也就是根据不确定是不是回文的区间[i + 1, j - 1],来判断了[i, j]是不是回文,那结果一定是不对的。所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的

  5. 举例推导dp数组

    以输入:"aaa"为例,dp数组状态如图:

    647.回文子串1

    图中有6个true,所以就是有6个回文子串。

    注意因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分

class Solution {
    public int countSubstrings(String s) {
        char[] chars = s.toCharArray();
        int len = chars.length;
        
        // dp[i][j] 表示[i,j]范围内的s是否是回文子串
        boolean[][] dp = new boolean[len][len];
        int result = 0;

        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                // 注意这里没有列出当s[i]与s[j]不相等的时候,
                // 因为在下面dp[i][j]初始化的时候,就初始为false。
                if (chars[i] == chars[j]) {
                    if (j - i <= 1) { // 情况一 和 情况二
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 情况三
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
}

动态规划的空间复杂度是偏高的,再看一下双指针法。

首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。

在遍历中心点的时候,要注意中心点有两种情况

一个元素可以作为中心点,两个元素也可以作为中心点。

class Solution {
    public int countSubstrings(String s) {
        int sum = 0;
        for (int i = 0; i < s.length(); i++) {
            sum += extend(s, i, i); // 以i为中心
            sum += extend(s, i, i + 1); // 以i和i + 1为中心
        }
        return sum;
    }

    int extend(String s, int left, int right) {
        int res = 0;
        // 防止索引越界
        while (left >= 0 && right < s.length()
               && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
            res++;
        }
        return res;
    }
}

类似题目:5. 最长回文子串


516. 最长回文子序列

题目链接:516. 最长回文子序列

思路:本题回文子序列相对于回文子串来说不要求连续,动态规划五步曲分析:

  1. dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]

  2. 递推公式:分情况讨论

    在判断回文子串的题目中,关键逻辑就是看s[i]与s[j]是否相同。如图: 516.最长回文子序列

    如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2

    如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入并不能增加[i, j]区间回文子序列的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。

    加入s[j]的回文子序列长度为dp[i + 1][j]

    加入s[i]的回文子序列长度为dp[i][j - 1]

    那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

    516.最长回文子序列1

  3. 初始化:可以看出递推公式计算不到 i 和 j 相同时的情况,所以需要手动初始化一下,当 i 与 j 相同,那么dp[i][j]一定是等于1的,即:一个字符的回文子序列长度就是1。其他情况dp[i][j]初始为0就行。

  4. 遍历顺序:

    从递归公式中,可以看出,dp[i][i]依赖于 dp[i + 1][j - 1],dp[i + 1][j]和dp[i][j - 1],如图:

    img

    所以遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的

    j的话,可以正常从左到右遍历。

  5. 举例推导dp数组

    以输入s:“cbbd” 为例,dp数组状态如图:

    516.最长回文子序列3

    红色框即:dp[0][s.length() - 1]为最终结果。

class Solution {
    public int longestPalindromeSubseq(String s) {
        char[] arr = s.toCharArray();
        // dp[i][j] 表示字符串s在[i,j]范围内最长回文子序列的长度
        int[][] dp = new int[arr.length][arr.length];

        // 初始化
        for (int i = 0; i < arr.length; i++) {
            dp[i][i] = 1;
        }

        for (int i = arr.length - 1; i >= 0; i--) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] == arr[j]) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }

        return dp[0][arr.length - 1];
    }
}

动态规划总结

动态规划五步曲对解动规题目至关重要!

动规五步曲分别为:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动规题目可以分为以下几种系列

  • 基础系列
  • 背包系列
  • 打家劫舍系列
  • 股票系列
  • 子序列问题系列

详细总结


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基础篇_开发web程序(C/S架构,SpringBoot,贷款计算器-WEB版)

文章目录 一. C/S 架构1. C/S 架构2. URL 格式 二. Spring Boot1. 向导生成2. 准备工作1) 修改版本2) 修改maven 设置 3. 导入模块4. hello world5. 处理输入页面接收参数练习 - 加法 三. 贷款计算器 - WEB 版1. 数组定义改写贷款计算器越界遍历默认值 2. 二维数组3. 贷款计算器…

PaddleSeg学习4——paddle模型使用TensorRT推理(c++)

paddle模型使用TensorRT推理 1 模型末端添加softmax和argmax算子2 paddle模型转onnx模型3 onnx模型转TensorRT模型3.1 安装TensorRT-8.5.3.13.2 使用 trtexec 将onnx模型编译优化导出为engine模型 4 TensorRT模型推理测试5 完整代码6 测试结果 1 模型末端添加softmax和argmax算…

SpringBoot 源码解析4:refresh 方法解析

SpringBoot 源码解析4&#xff1a;refresh 方法解析 1. refresh 方法解析2. 准备刷新 AbstractApplicationContext#prepareRefresh3. 获取bean工厂 AbstractApplicationContext#obtainFreshBeanFactory4. 准备bean工厂 AbstractApplicationContext#prepareBeanFactory5. Servle…

Dell 机架式服务器 - 高级定制

Dell 机架式服务器 - 高级定制 1. Dell Technologies2.1. Servers & Storage (服务器及存储) -> Servers2.2. Rack Servers (机架式服务器)2.3. Shop2.4. PowerEdge Rack Servers (PowerEdge 机架式服务器)2.5. PowerEdge R760 Rack Server (PowerEdge R760 机架式服务器…

边缘计算:连接实时数据的力量与未来发展之路

边缘计算是一种分布式计算范式&#xff0c;它旨在将数据处理、存储和应用服务带到数据源的近端&#xff0c;即网络的“边缘”。在边缘计算模型中&#xff0c;算力和存储资源距离末端用户或数据源更近&#xff0c;这减少了数据在网络中传输的距离&#xff0c;从而降低延迟&#…

BikeDNA(四)初始化参考数据

BikeDNA&#xff08;四&#xff09;初始化参考数据 这本笔记本&#xff1a; 加载定义研究区域的多边形&#xff0c;然后为研究区域创建网格叠加。加载参考数据。处理参考数据以创建分析所需的网络结构和属性。 先决条件和条件 输入/输出 config.yml 必须提前设置。 此笔记本…

uniapp 查找不到uview-ui文件怎么办?

用官方的方式总是报&#xff1a;文件查找失败&#xff1a;uview-ui at main.js 解决方案&#xff1a; 1.先安装uview-ui npm install uview-ui 下载成功是这样的&#xff1a; 而不是这样的&#xff1a; 这样的原因是你的项目里没有package.json包&#xff0c;先执行 npm …

Ps:操控变形

Ps菜单&#xff1a;编辑/操控变形 Edit/Puppet Warp 操控变形 Puppet Warp命令能够借助网格随意扭曲特定图像区域&#xff0c;同时可保持其他区域不变。 其应用范围小至精细的图像修饰&#xff08;如发型设计&#xff09;&#xff0c;大至总体的变换&#xff08;如重新定位手臂…

Ftrans飞驰云联荣获“CSA 2023安全创新奖”

2023年12月21日&#xff0c;第七届云安全联盟大中华区大会在深圳成功举办。会上&#xff0c;CSA大中华区发布了多个研究成果并进行 CSA 2023年度颁奖仪式&#xff0c;Ftrans飞驰云联以其突出的技术创新能力和广泛的市场应用前景&#xff0c;荣获备受瞩目的“CSA 2023安全创新奖…

watchdog,一个无敌的 Python 库

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个无敌的 Python 库 - watchdog。 Github地址&#xff1a;https://github.com/gorakhargosh/watchdog 在软件开发和系统管理领域&#xff0c;经常需要监控文件和目录的变化&#xff0c;以便在文…

JDBC PrepareStatement 的使用(附各种场景 demo)

在 Java 中&#xff0c;与关系型数据库进行交互是非常常见的任务之一。JDBC&#xff08;Java Database Connectivity&#xff09;是 Java 平台的一个标准 API&#xff0c;用于连接和操作各种关系型数据库。其中&#xff0c;PreparedStatement 是 JDBC 中的一个重要接口&#xf…

abaqus重新打开之后自定义的工具栏状态恢复默认的解决办法

在自定义工具栏之后&#xff0c;点击&#xff1a; File——Save Display Options——勾选Current&#xff0c;点击OK。 中文版&#xff1a;文件-保存显示选项-目录选择当前目录&#xff0c;点击确定。 重新打开abaqus之后发现工具栏是自己定义的。 另&#xff1a; 1. 视口注…

brpc: a little source code

之前在https://www.yuque.com/treblez/qksu6c/nqe8ip59cwegl6rk?singleDoc# 《olap/clickhouse-编译器优化与向量化》中我谈过brpc的汇编控制bthread。本文就来看一下brpc作为一个高性能的rpc实现&#xff0c;除了自定义线程栈之外&#xff0c;代码还有什么优秀之处。 因为时间…

# C++系列-第3章循环结构-28-累加

在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 累加 奥运奖牌计数 题目描述 2008 2008 2008 年北京奥运会&#xff0c;A 国的运动员参与了 n n n 天的决赛项目 ( 1 ≤ n ≤ 100 ) (1 \le n \le 100) (1≤n≤100)。现在要统计一下 A 国所获得的…

uniapp小程序超出一行显示...并展示更多按钮

注意:全部标签需要浮动在父盒子右边哦 循环获取所有需要展示数据标签的高度 this.goods this.goods.map(item > ({...item,showBtn: false}));this.$nextTick(() > {uni.createSelectorQuery().in(this).selectAll(".cart-info").boundingClientRect((data)…

亚马逊云科技 WAF 部署小指南(五):在客户端集成 Amazon WAF SDK 抵御 DDoS 攻击...

方案介绍 在 WAF 部署小指南&#xff08;一&#xff09;中&#xff0c;我们了解了 Amazon WAF 的原理&#xff0c;并通过创建 WEB ACL 和托管规则防护常见的攻击。也了解了通过创建自定义规则在 HTTP 请求到达应用之前判断是阻断还是允许该请求。在 Amazon WAF 自定义规则中&am…

【ACL 2023】 The Art of Prompting Event Detection based on Type Specific Prompts

【ACL 2023】 The Art of Prompting: Event Detection based on Type Specific Prompts 论文&#xff1a;https://aclanthology.org/2023.acl-short.111/ 代码&#xff1a;https://github.com/VT-NLP/Event_APEX Abstract 我们比较了各种形式的提示来表示事件类型&#xff0…

STM32CubeMX配置STM32G071UART+DMA收发数据(HAL库开发)

时钟配置HSI主频配置64M 配置好串口&#xff0c;选择异步模式 配置DMA TX,RX,选择循环模式。 NVIC中勾选使能中断 勾选生成独立的.c和h文件 配置好需要的开发环境并获取代码 串口重定向勾选Use Micro LIB main.c文件修改 增加头文件和串口重定向 #include <string.h&g…

thinkphp6报错Driver [Think] not supported.

thinkphp6报错Driver [Think] not supported. 问题解决方法测试 问题 直接使用 View::fetch();渲染模板报错 解决方法 这个报错是由于有安装视图驱动造成的 运行如下命令安装即可 composer require topthink/think-view官方文档中是这么写的 视图功能由\think\View类配合视…

Python集合(set)

目录 集合创建集合访问集合向集合中添加和删除元素集合的 交集&#xff0c;并集&#xff0c;差集运算**交集****并集****差集** 集合方法 集合 集合是无序和无索引的集合。在 Python 中&#xff0c;集合用花括号编写。 创建集合 创建集合&#xff1a; thisset {"a"…