Day57【动态规划】647.回文子串、516.最长回文子序列

647.回文子串

力扣题目链接/文章讲解

视频讲解

1、确定 dp 数组下标及值含义

dp[i][j]:表示区间范围为 [i, j] 的子串是否为回文串(j >= i)

这样定义才方便我们的递推!怎么想到的?回文串需要对比串的两端,用二维 dp 数组表示串的两端元素的对比情况 

2、确定递推公式

整体上是两种,就是 s[i] 与 s[j] 相等,s[i] 与 s[j] 不相等这两种

当 s[i] 与 s[j] 不相等,那没啥好说的了,dp[i][j] 一定是 false

当 s[i] == s[j],有如下三种情况:

  1. i == j,此时区间范围 [i, j] 就只有一个元素,当然是回文子串,dp[i][j] 为 true
  2. i + 1 = j,此时区间范围 [i, j] 为两个相同元素,也是回文子串,dp[i][j] 也为true
  3. i 与 j 相差大于 1 的时候,例如 cabac,此时 s[i] 与 s[j] 已经相同了,我们看区间 [i, j] 是不是回文子串就看区间 [i + 1, j - 1],即 aba 是不是回文就可以了

上述情况代码如下

if (s[i] == s[j]) {
    if (j - i <= 1) { // 情况一 和 情况二
        dp[i][j] = true;
    } else if (dp[i + 1][j - 1] == true) { // 情况三
        dp[i][j] = true;
    }
}

3、dp 数组初始化

全部初始化为 false,在遍历填充过程中不断发现回文子串并置 true

4、确定遍历顺序 

根据递推公式看出,dp[i][j] 依赖于其左下方的 dp 值,所以一定要从下到上,从左到右遍历,才能保证 dp[i + 1][j - 1] 都是经过计算的 

5、打印 dp 数组验证

代码如下

class Solution {
public:
    int countSubstrings(string s) {

        vector<vector<bool> > dp(s.size(), vector<bool>(s.size(), false));

        for (int i = s.size() - 1; i >= 0; --i) {    // 从下往上,从左往右遍历
            for (int j = i; j < s.size(); ++j) {    // 保证j>=i
                if (s[i] == s[j]) {    
                    if (j - i <= 1)
                        dp[i][j] = true;
                    else if (dp[i + 1][j - 1] == true)
                        dp[i][j] = true;
                }
            }
        }

        int res = 0;    // dp[i][j]为true,就表示子串[i, j]为回文子串,统计dp数组中true的数量即可统计出回文子串的数量
        for (const auto & line : dp)
            for (const auto item : line)
                if (item == true)
                    ++res;
        
        return res;
    }
};

516.最长回文子序列 

力扣题目链接/文章讲解

视频讲解

1、定义 dp 数组下标及值含义

dp[i][j]:表示字符串 s 在 [i, j] 范围内的最长回文子序列的长度为 dp[i][j](j >= i)

回文序列需要对比序列的两端,用二维 dp 数组表示两端元素的对比情况

2、确定递推公式

关键逻辑就是看 s[i] 与 s[j] 是否相同

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

如果 s[i] != s[j],则 s[i, j] 的最长回文子序列不可能同时包含 s[i] 和 s[j],那么,长度一定为下面两种情况之一

  • 只考虑在 s[i, j - 1] 中寻找最长回文子序列,这样能够保证找到的回文子序列不可能同时包含 s[i] 和 s[j]
  • 同理,也可以只考虑在 s[i + 1, j] 中找最长回文子序列,这样也能保证找到的回文子序列不可能同时包含 s[i] 和 s[j]

因为找的“最长”回文子序列,上面两种情况取一个最大值,如图 

3、dp 数组初始化

感觉代码随想录讲得不够清晰,我们来逐步推导确定有哪些部分我们需要初始化,以及应该初始化为多少

根据定义我们看出,dp[i][j]:表示字符串 s 在 [i, j] 范围内的最长回文子序列的长度,其中 j >= i

那么,最终我们的 dp 数组也只需要去遍历填充 j >= i 的部分,因为其他部分没有意义

又根据递推公式看出,我们想要推导 dp[i. j], 需要依赖于其左方、下方、左下方的 dp 值

因此,为了能够遍历填充满绿色部分,我们需要初始化红色对角线部分与紫色部分的值,如下图所示 

对角线红色部分的 dp[i][j]:当 i 与 j 相同,那么 dp[i][j] 一定是等于 1 的,即:一个字符的回文子序列长度就是 1

紫色部分的 dp[i][j]:没有意义。对于这种没有实际意义的不知道如何初始化的,可以根据递推公式判断应该初始化为多少 

哪里用到了紫色部分的值?看图,当 j = i + 1 时,如果 s[i] == s[j],那么 dp[i][j] = dp[i + 1][j - 1] + 2,这个时候会用到紫色部分的 dp 值。显然,当 j = i + 1 且 s[i] == s[j] 时,即:两个相同字符构成串的最长回文子序长度就是 2,再带回递推公式,可以看出紫色部分应该初始化为 0

其他部分随意初始化,反正会被覆盖

4、确定遍历顺序

从下往上遍历 i,从左往右遍历 j,这样才能保证 dp[i][j] 所依赖的数据是被更新后的正确 dp 值

5、打印 dp 数组验证

代码如下

class Solution {
public:
    int longestPalindromeSubseq(string s) {

        vector<vector<int> > dp(s.size(), vector<int>(s.size(), 123));  // 这里初始化为123表示“其他部分可以随意初始化”

        for (int i = 0; i < s.size(); ++i) {    // 初始化对角线
            dp[i][i] = 1;
        }

        for (int i = 1; i < s.size(); ++i) {    // 初始化紫色部分
            dp[i][i - 1] = 0;
        }

        for (int i = s.size() - 2; i >= 0; --i) // 从下往上,从左往右遍历填充
            for (int j = i + 1; j < s.size(); ++j) {    // 仅需填充未被初始化的部分,看图
                if (s[i] == s[j])
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                else
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
            }

        return dp[0][s.size() - 1]; // 表示字符串s在[0, s.size()-1]范围内的最长回文子序列的长度
    }
};

回顾总结 

动态规划结束,定义 dp 数组和递推是关键

动态规划五部曲贯穿始终 

  1. 确定 dp 数组下标及值的含义
  2. 确定递推公式
  3. dp 数组如何初始化
  4. 确定遍历顺序
  5. 打印 dp 数组验证

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

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

相关文章

用可编程逻辑器件FPGA LCMXO2-4000HC-6MG132I 实现智能汽车解决方案设计

LCMXO2-4000HC-6MG132I lattice莱迪斯深力科 MachXO2 可编程逻辑器件 (PLD) 由六个超低功耗、即时启动、非易失性 PLD 组成&#xff0c;可提供 256 至 6864 个查找表 (LUT) 的密度。 MachXO2 系列 PLD 提供多种特性&#xff0c;例如嵌入式块 RAM (EBR)、分布式 RAM 和用户闪存 …

基于html+css的图片展示93

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Spring Boot注解@Async与线程池的配置

目录 使用异步注解创建异步任务 Async注解 使用Demo 线程池配置 Spring Boot默认用于异步任务线程池配置 线程池配置 线程池隔离 为什么需要线程池隔离&#xff1f; 线程池隔离实现Demo 线程池配置&#xff1a; 异步任务&#xff1a; 测试demo 参考内容&#xff1a; 使…

Modern CSV:大型 CSV 文件编辑器/查看器 Crack

Modern CSV用于快速查看大型 CSV 文件 适用于 Windows、Mac 和 Linux 的复杂 CSV 编辑器/查看器 被使用 电子商务运营商。数据科学家。会计师。 IT 专业人员。学生。医学研究人员。数字营销人员。生物学家。工程师。 现代 CSV 是适用于 Windows、Mac 和 Linux 的功能强大的表格…

面向对象编程 实验三 sduwh 子窗口与控件的基本用法、资源的使用 参考实验报告1

源自网络收集&#xff0c;仅供参考 实验三收集到两份完整报告&#xff0c;这是其一&#xff0c;另一份见本专栏下一篇文章。 实验题目 《面向对象程序设计》 实验三 实验题目&#xff1a;子窗口与控件的基本用法、资源的使用 整体目的&#xff1a;理解、窗口之间的消息传送…

【Netty】Netty中的超时处理与心跳机制(十九)

文章目录 前言一、超时监测二、IdleStateHandler类三、ReadTimeoutHandler类四、WriteTimeoutHandler类五、实现心跳机制5.1. 定义心跳处理器5.2. 定义 ChannelInitializer5.3. 编写服务器5.4. 测试 结语 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#…

内存栈与CPU栈机制

1. 内存栈: 先入后出,LIFO(LAST IN FIRST OUT) 入栈:将一个新的元素放到栈顶 出栈:从栈顶取出一个元素 栈顶元素总是最后一个入栈,需要时出栈. 2.CPU栈机制 8086CPU提供相关指令以栈方式来访问内存空间.相当于将一段内存当做栈来使用 8086CPU提供的入栈指令为:PUSH ,出栈指令为…

JointJS+ v3.7 Crack

JointJS v3.7 改进了对 SVG 上下文中的外部对象的支持。 2023 年 5 月 30 日 - 16:00 新版本 特征 改进了对外部对象 (HTML) 的支持- 外部对象已成为 Web 开发的标准&#xff0c;JointJS 现在已经在 SVG 上下文中引入了对外部对象的全面且功能齐全的支持。这意味着您现在可以在…

Elasticsearch 8.8.0 发布

Elasticsearch 是一个基于 Lucene 库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎&#xff0c;具有 HTTP Web 接口和无模式 JSON 文档。Elasticsearch 基于 Java 开发&#xff0c;并在 SSPL Elastic License 双重授权许可下作为开源软件发布。 Elasticsearch 8…

win10系统如何设置虚拟回环

在日常生活中&#xff0c;人们(特别是IT行业者)通常需要在一台机上进行软件测试&#xff0c;而同一台计算上通常只能使用一个地址&#xff0c;而在需要同时使用两个地址进行测试的时候就显得捉襟见肘。此方法通过配置window10自带的环回适配器&#xff0c;达到上述目的。 win1…

如何使用Kali进行信息收集?

渗透测试即模拟黑客入侵的手段对目标网络进修安全测试&#xff0c;从而发现目标网络的漏洞&#xff0c;对目标网络进行安全加固与漏洞修复。 Kali 是一个基于 debian 的渗透测试平台&#xff0c;其中集成了很多常见的和不常见的渗透测试工具&#xff0c;如下图&#xff1a; 工…

基于SSM的服装设计供需系统设计与实现

摘 要&#xff1a;作为服装设计的重要形式之一&#xff0c;服装具有显著的审美性&#xff0c;是人类情感表达不可忽视的代表形态。但在新时期背景下&#xff0c;随着服装设计的进一步优化&#xff0c;服装设计创新融合强度也随之增强。本文就服装设计供需系统进行深入探究。 服…

Linux之命令搜索

目录 Linux之命令搜索 Whereis命令 定义 基本信息 举例 which命令 定义 与whereis命令的区别 基本信息 举例 locate 命令 定义 优点 缺点 基本信息 案例 Linux之命令搜索 Whereis命令 定义 whereis --- 搜索系统命令的命令&#xff08;像绕口令一样&#xff09…

数据库新闻速递 明白3中主流的数据迁移方法 (译)

头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共8…

ShardingSphere笔记(三):自定义分片算法 — 按月分表·真·自动建表

ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表 文章目录 ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表一、 前言二、 Springboot 的动态数据库三、 实现我们自己的动态数…

MySQL查询当前数据和上一行数据比较、业务数据的趋势分析、数据变动的监控和报警

标题: 使用MySQL查询当前数据和上一行数据比较的场景 在MySQL中&#xff0c;我们经常需要对数据进行比较和分析。其中一种常见的需求是查询数据列表并与前一行的数据进行比较。这种场景可以通过使用窗口函数或连接来实现。本文将介绍使用MySQL查询比较数据和上一行数据的场景&a…

计算机组成原理-指令系统-指令格式及寻址方式

目录 一、指令的定义 1.1 扩展操作码指令格式 二、指令寻址方式 2.1 顺序寻址 2.2 跳跃寻址 三、 数据寻址 3.1 直接寻址 3.2 间接寻址 3.3 寄存器寻址 ​ 3.4 寄存器间接寻址 3.5 隐含寻址 3.6 立即寻址 3.7 偏移地址 3.7.1 基址寻址 3.7.2 变址寻址 3.7.3 相对寻址…

【C++】右值引用和移动语义(详细解析)

文章目录 1.左值引用和右值引用左值引用右值引用 2.左值引用和右值引用的比较左值引用总结右值引用总结 3.右值引用的使用场景和意义知识点1知识点2知识点3知识点4总结 4.完美转发万能引用见识完美转发的使用完美转发的使用场景 1.左值引用和右值引用 传统的C语法中就有引用的…

【SpringCloud】SpringAMQP总结

文章目录 1、AMQP2、基本消息模型队列3、WorkQueue模型4、发布订阅模型5、发布订阅-Fanout Exchange6、发布订阅-DirectExchange7、发布订阅-TopicExchange8、消息转换器 1、AMQP Advanced Message Queuing Protocol&#xff0c;高级消息队列协议。是用于在应用程序之间传递业务…

Java设计模式(三)

系列文章目录 迪米特法则 合成复用原则 设计原则核心思想 文章目录 系列文章目录前言一、迪米特法则1.迪米特法则基本介绍2.迪米特法则注意事项和细节 二、合成复用原则1.合成复用原则基本介绍 三、设计原则核心思想总结 前言 大家好呀&#xff0c;欢迎来到柚子的博客~让我们…
最新文章