力扣 | 动态规划 | 在字符串的应用 | 最长回文子串、最长回文子序列、单词拆分、编辑距离

news2024/11/26 18:46:32

文章目录

  • 1.最长回文子串
  • 2.最长回文子序列
  • 3.单词拆分
  • 4.编辑距离
  • 5. 共同点和思路
  • 6. 各个问题的思路和扩展
    • 1. 最长回文子串
    • 2. 最长回文子序列
    • 3. 单词拆分
    • 4. 编辑距离

在解答字符串动态规划的应用时,我们需要非常注意一个问题:
  有时候我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第一个字符串的第i个字符,第二个字符串的第j个字符。 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]表示两个都为空串时。
  使用数组下标访问时,应该这样访问第一个字符串的第i个字符:word1[i - 1]
  总的来说dp的定义可能和数组访问下标不一样。

if(word1[i - 1] == word2[j - 1])
	dp[i][j] = ···

我们还需要这样思考:为什么要使用动态规划?不使用其他方法?为什么动态规划可以解决?

1.最长回文子串

LeetCode:5.最长回文子串
在这里插入图片描述
一个回文串 s t r [ i ] [ j ] str[i][j] str[i][j]的子串有这样的性质: s t r [ i + 1 ] [ j − 1 ] str[i+1][j-1] str[i+1][j1]也是回文串。

因此我们要判断一个子串 s t r [ i ] [ j ] str[i][j] str[i][j]是否是回文串,我们只需要知道其子串 s t r [ i + 1 ] [ j − 1 ] str[i+1][j-1] str[i+1][j1]是否是回文串即可。我们使用 d p [ i ] [ j ] dp[i][j] dp[i][j]保存该区间[i,j]是否是回文串。

这样我们就使用回文字符串常用的解题方式——从长度为1的子串开始判断是否为回文串,判断完后长度加一,这样每次都能保证判断时,其子串都已经知道是否是回文串了。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    string longestPalindrome(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));//dp[i]表示以i结尾的 最长回文子串
        dp[0][0] = true;
        
        int left = 0, len = 1;
        for(int j = 1; j < s.size(); ++ j){
            int tmp = j - 1;
            dp[j][j] = true;
            if(s[tmp] == s[j]) {dp[tmp][j] =true;left = tmp; len = 2;}
        }
        for(int i = 2; i < s.size(); ++ i){//距离
            for(int j = i; j < s.size(); ++ j){
                int tmp = j - i;
                if(s[j] == s[tmp] ){
                    if(dp[tmp + 1][j - 1] == true) {dp[tmp][j] = true;left = tmp; len = i + 1;}
                }
            }
        }

        return s.substr(left, len);
    } 
};

2.最长回文子序列

LeetCode:2.最长回文子序列
在这里插入图片描述
与最长回文子串类似,本题的区别是,不要求子串严格是回文串,只需要包含回文串即可。

因此我们要判断一个子串 s t r [ i ] [ j ] str[i][j] str[i][j]是否包含回文串,我们只需要知道其子串 s t r [ i + 1 ] [ j − 1 ] str[i+1][j-1] str[i+1][j1]是否包含回文串即可。我们使用 d p [ i ] [ j ] dp[i][j] dp[i][j]保存该区间[i,j]是包含的最长回文串长度。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.length();
        if(n == 1) return 1;
        vector<vector<int>> dp(n, vector<int>(n));
        for(int i = 0; i < n; ++ i) {dp[i][i] = 1;}

        for(int i = 1; i < n; ++ i){
            for(int j = i; j < n; ++ j){
                int l = j - i;
                char c1 = s[l], c2 = s[j];
                if(c1 == c2){
                    dp[l][j] = dp[l + 1][j - 1] + 2;
                }else{
                    dp[l][j] = max(dp[l][j - 1], dp[l + 1][j]);
                }
            }
        }
        return dp[0][n - 1];
    }
};

3.单词拆分

LeetCode:139. 单词拆分
在这里插入图片描述
这个拼接的顺序不是随便的,比如 c a t s a n d catsand catsand,字典为 [ " c a t " , " c a t s " , " a n d " ] ["cat","cats","and"] ["cat""cats""and"],如果先用 " c a t " "cat" "cat"去拼接,会导致问题无解,但实际上有解。

定义 d p [ i ] dp[i] dp[i]表示,前 i i i个字符是否可以被拼接,那么它能被拼接的话,我们用字典里面的词一个一个放到末尾,然后看 d p [ i − d i c t . s i z e ( ) ] dp[i - dict.size()] dp[idict.size()]能否被拼接就能知道 d p [ i ] dp[i] dp[i]能否被拼接了。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for(int i = 1; i <= s.size(); ++ i)
            for(auto & str : wordDict)
                if(str.size() <= i && dp[i - str.size()] == true)
                    if(s.substr(i - str.size(), str.size()) == str){
                        dp[i] = true;
                        break;
                    }

        return dp[s.size()];
    }
};

4.编辑距离

LeetCode:72.编辑距离
在这里插入图片描述
其实动态规划问题就是思考出状态以及状态转移,比如这里你想要知道 w o r d 1 word1 word1 w o r d 2 word2 word2转换次数,你就只需要知道其子串。

if(word1[i - 1] == word2[j - 1]){
    dp[i][j] = dp[i - 1][j - 1];
}else{
    dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
}
class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));

        for(int i = 1; i <= word1.size(); ++ i){
            dp[i][0] = i;
        }
        for(int i = 1; i <= word2.size(); ++ i){
            dp[0][i] = i;
        }

        for(int i = 1; i <= word1.size(); ++ i){
            for(int j = 1; j <= word2.size(); ++ j){
                if(word1[i - 1] == word2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1];
                }else{
                    dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
                }
            }
        }

        return dp[word1.size()][word2.size()];
    }
};

5. 共同点和思路

这些字符串相关的动态规划问题有一些共同的特点:

  1. 定义状态

    • 使用二维数组 dp 表示两个子串之间的某种状态(如是否回文、最长子序列长度、是否可以拼接、编辑距离等)。
  2. 状态转移

    • 根据问题的具体要求,定义状态转移方程,用子问题的解构建原问题的解。
  3. 初始化

    • 根据问题的初始条件,初始化 dp 数组的边界值。
  4. 遍历顺序

    • 通常使用双重循环遍历所有可能的子串或子序列。

6. 各个问题的思路和扩展

1. 最长回文子串

思路

  • 定义 dp[i][j] 表示字符串 s 从第 i 到第 j 的子串是否为回文串。
  • 如果 s[i] == s[j],并且 dp[i+1][j-1] 为真,则 dp[i][j] 为真。
  • 初始化单个字符的子串和长度为2的子串。

扩展

  • 可以使用中心扩展法来优化时间复杂度,从每个字符和字符间隙向两边扩展检查回文。

2. 最长回文子序列

思路

  • 定义 dp[i][j] 表示字符串 s 从第 i 到第 j 的子串中最长回文子序列的长度。
  • 如果 s[i] == s[j],则 dp[i][j] = dp[i+1][j-1] + 2
  • 否则,dp[i][j] = max(dp[i+1][j], dp[i][j-1])

扩展

  • 可以尝试空间优化,将二维数组优化为一维数组。

3. 单词拆分

思路

  • 定义 dp[i] 表示字符串 s 的前 i 个字符能否被拆分成字典中的单词。
  • 对于每个位置 i,检查从 ji 的子串是否在字典中,并且 dp[j] 是否为真。

扩展

  • 可以使用记忆化搜索或递归的方法来优化复杂度。

4. 编辑距离

思路

  • 定义 dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少操作数。
  • 如果 word1[i-1] == word2[j-1],则 dp[i][j] = dp[i-1][j-1]
  • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1

扩展

  • 可以尝试空间优化,将二维数组优化为一维数组。
  • 可以扩展到其他字符串变换问题,如最长公共子序列。

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

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

相关文章

MoonBit 周报 Vol.53:新增高级循环语法、引入字符串插值、MoonBit AI 支持代码解释!

weekly 2024-08-05 MoonBit更新 添加了基于 Iter 和 Iter2 类型的 for .. in 循环支持&#xff1a; fn main {for x in [ 1, 2, 3 ] {println(x)}for k, v in { "x": 1, "y": 2 } {println("\{k} > \{v}")} }for 与 in 之间可以使用 1&…

事务及事务的控制.特性

一.事务 1.事务定义 ​ 逻辑上多个DML操作形成的一个整体,多个DML操作要么全都执行成功,要么全都执行失败,如果是DDL的操作会自动提交事务. 2.事务的控制 命令 start transaction开启事务rollback 回滚commit提交事务 mysql默认自动提交事务,每个DML执行完毕后,直接提交事…

【Linux】编译器gcc/g++ 、程序翻译过程、动静态库

目录 1.gcc/g Linux编译器1.1. gcc与g的安装1.2. gcc与g用法1.2.1.gcc用法1.2.2. g用法 1.3. 程序翻译的过程1.3.1. 前提知识&#xff1a;1.3.2. 预处理&#xff08;语言种类不变&#xff09;条件编译用途&#xff1a; 1.3.3. 编译&#xff08;生成汇编语言&#xff09;1.3.4. …

性能测试工具之JMeter

JMeter Apache JMeter应用程序是开源软件,是一个100%纯Java应用程序,旨在负载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的,但后来扩展到其他测试功能。 JMeter是一个免费、开源、跨平台的性能测试工具,于20世纪90年代后期面世。这是一个成熟、健全且具有…

‘Task‘ object is not callable ERROR

pycharm 调试异步的代码报错 TypeError: ‘Task‘ object is not callable ERROR: Exception in callback &#xff1c;Task 解决方法&#xff1a;点击菜单栏帮助-查找操作-注册表&#xff0c;在注册表中搜索python.debug.asyncio.repl禁用即可

卷大模型,还是卷应用?一次看明白

自从ChatGPT横空出世以来&#xff0c;中美之间围绕大模型的科技竞争愈演愈烈&#xff0c;也渐渐分化出两条差异化发展路线&#xff1a;一派侧重将AI能力投入应用场景&#xff0c;另一派则侧重让基础模型能力更强。于是&#xff0c;“卷应用”还是“卷大模型”成为中国许多新入场…

phpmailer如何配置SSL以发送安全电子邮件?

phpmailer支持哪些邮件附件类型&#xff1f;如何使用phpmailer&#xff1f; PHPMailer作为一个广泛使用的PHP邮件发送类库&#xff0c;支持通过SSL发送加密邮件&#xff0c;以确保信息在传输过程中的安全性。AokSend将探讨如何配置PHPMailer以使用SSL发送安全的电子邮件。 Ph…

M12防水分线盒双通道PNP型8路预铸线缆半导体制造设备

M12防水分线盒双通道PNP型8路预铸线缆&#xff0c;作为工业自动化设备中关键的组件之一&#xff0c;在提升系统稳定性与可靠性方面发挥着至关重要的作用。以钡铼技术DB系列为例&#xff0c;这款8路M12双通道预铸线缆分线盒在设计和材料选择上均体现了工业级的高标准&#xff0c…

解决方案:如何在虚拟机中扩展 Windows 11 C 盘,绕过恢复分区的限制!

第2章 背景 不管是虚拟化还是物理机&#xff0c;我们安装Windows 11 时候经常遇到无法直接扩容盘&#xff0c;我们简单介绍下Windows中C盘组成。 ### 恢复分区的目的 恢复分区是 Windows 操作系统安装过程中创建的一个特殊分区&#xff0c;主要用于以下目的&#xff1a; 1. …

如何修复 VLC 无法播放 MKV 问题

VLC 是一个媒体播放器&#xff0c;您可以用它来播放各种格式的音频或视频文件。但有时&#xff0c;您无法在 VLC 媒体播放器上运行 MKV 文件。问题可能是黑屏、没有声音、视频断断续续或错误消息&#xff08;例如“无法识别的编解码器&#xff1a;VLC 无法识别音频或视频编解码…

自助创建 1Panel 应用

自助创建 1Panel 应用 前言 1Panel 作为一款开源的 Linux 服务器运维管理面板&#xff0c;其优质的 应用商店 想必也是很多人喜爱它的原因&#xff0c;除了官方的 应用列表 &#xff0c;开源社区内也涌现出了许多优质的第三方应用商店资源&#xff0c;比如 okxlin/appstore 等…

【MySQL】什么是索引?了解索引的底层原理

索引的概念 索引是一种用于提高数据库查询效率的数据结构。它类似于书籍的目录&#xff0c;通过快速定位数据的方式&#xff0c;减少了数据检索的时间。索引在数据库表中可以被看作是一个指向数据的指针&#xff0c;它们存储了列的值及其对应行的位置&#xff0c;从而使得数据…

《老俞闲话|唯爱和热情不可辜负》读后感

《老俞闲话&#xff5c;唯爱和热情不可辜负》读后感 俞敏洪先生的这篇讲话充满了深情与智慧&#xff0c;他以自己丰富的人生经历和教育实践&#xff0c;向我们展现了一位教育家对于教育事业的热爱和对教师角色的深刻理解。 情感真挚&#xff0c;触动人心 俞敏洪先生的讲话中流…

嵌入式day25

进程线程 多任务编程 1、进程 2、线程 进程&#xff08;process&#xff09; 进行中的程序 --- 正在运行中的程序 进程 --- 程序的一次执行的过程 进程 是程序的一个实例 进程是跑起来的程序 一个程序可以对应多个进程 程序 静态 硬盘 进程 动态 内存 为什么需要进程…

ECharts 数据可视化 入门基本知识 下载安装常用的图表 【1】

ECharts一个基于 JavaScript 的开源可视化图表库&#xff0c;即将数据以图形或图像的方式展现成在屏幕上显示出来&#xff0c;这种方式称为数据可视化。数据可视化有助于我们分析数据&#xff0c;帮助我们更深入更直观的理解数据。今天回顾顺便总结一下echarts的基本知识&#…

基于SpringBoot和Vue框架的体育馆场地预约系统的设计与实现---附源码15155

摘要 本研究设计和开发了一款体育馆场地预约系统。该系统旨在提供一个方便、高效的方式&#xff0c;让用户能够轻松预约体育馆场地&#xff0c;并实现对预约信息的管理与统计。系统主要分为普通用户和管理员两个角色。普通用户可以浏览通知公告、体育资讯&#xff0c;查看各个体…

并发编程(第二天)

interrupt 方法详解 打断 sleep&#xff0c;wait&#xff0c;join 的线程 这几个方法都会让线程进入阻塞状态 打断 sleep 的线程, 会清空打断状态打断正常运行的线程 打断正常运行的线程, 不会清空打断状态打断 park 线程 打断 park 线程, 不会清空打断状态 如果打断标记已经…

Apache Tomcat服务器版本号隐藏

渗透测试时发现有一台服务器的404报错页面中&#xff0c;有Apache Tomcat的版本号信息显示&#xff0c;发生了信息泄露&#xff0c;可能导致服务器被攻击。如下所示&#xff1a; 解决步骤如下&#xff1a; 1. 隐藏HTTP响应头中的Server信息 Tomcat默认会在HTTP响应头中包含S…

Java面试八股之消息队列通常由哪些角色组成

消息队列通常由哪些角色组成 消息队列系统通常涉及几个核心角色&#xff0c;这些角色协同工作以实现消息的传递和处理。主要的角色包括&#xff1a; 生产者&#xff08;Producer&#xff09;&#xff1a; 生产者是消息的创建者&#xff0c;负责将消息发送到消息队列中。生产者…

【八数码】

题目 错误代码 #include<bits/stdc.h> using namespace std; typedef pair<string, int> PII; #define x first #define y secondstring aim "12345678x"; int ans 0x3f3f3f3f; unordered_map<string, int> m; void bfs(string s, int pos) {qu…