代码随想录算法训练营 day56 | 动态规划 647. 回文子串 516.最长回文子序列

news2024/11/23 12:14:14

day56

      • 647 回文子串
        • 1.确定dp数组(dp table)以及下标的含义
        • 2.确定递推公式
        • 3,dp数组如何初始化
        • 4.确定遍历顺序
        • 5.举例推导dp数组
      • 516 最长回文子序列
        • 1.确定dp数组(dp table)以及下标的含义
        • 2.确定递推公式
        • 3.dp数组如何初始化
        • 4.确定遍历顺序
        • 5.举例推导dp数组

647 回文子串

题目链接
解题思路:
动规五部曲:

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

如果大家做了很多这种子序列相关的题目,在定义dp数组的时候 很自然就会想题目求什么,我们就如何定义dp数组。

绝大多数题目确实是这样,不过本题如果我们定义,dp[i] 为 下标i结尾的字符串有 dp[i]个回文串的话,我们会发现很难找到递归关系。

dp[i]dp[i-1]dp[i + 1] 看上去都没啥关系。

所以我们要看回文串的性质。 如图:
在这里插入图片描述

我们在判断字符串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]相等,s[i]s[j]不相等这两种。

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+1j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。

以上三种情况分析完了,那么递归公式如下:

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

result就是统计回文子串的数量。

注意这里我没有列出当s[i]s[j]不相等的时候,因为在下面dp[i][j]初始化的时候,就初始为false。

3,dp数组如何初始化

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

所以dp[i][j]初始化为false。

4.确定遍历顺序

遍历顺序可有有点讲究了。

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

dp[i + 1][j - 1]dp[i][j]的左下角,如图:
在这里插入图片描述

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

所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。

有的代码实现是优先遍历列,然后遍历行,其实也是一个道理,都是为了保证dp[i + 1][j - 1]都是经过计算的。

代码如下:

for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍历顺序
    for (int j = i; j < s.size(); j++) {
        if (s[i] == s[j]) {
            if (j - i <= 1) { // 情况一 和 情况二
                result++;
                dp[i][j] = true;
            } else if (dp[i + 1][j - 1]) { // 情况三
                result++;
                dp[i][j] = true;
            }
        }
    }
}

5.举例推导dp数组

举例,输入:“aaa”,dp[i][j]状态如下:
在这里插入图片描述

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

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

以上分析完毕,C++代码如下:

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍历顺序
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (j - i <= 1) { // 情况一 和 情况二
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 情况三
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
};

516 最长回文子序列

题目链接
解题思路:
动规五部曲分析如下:

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

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

2.确定递推公式

在判断回文子串的题目中,关键逻辑就是看s[i]与s[j]是否相同。

如果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]);

代码如下:

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]);
}

3.dp数组如何初始化

首先要考虑当i 和j 相同的情况,从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 ij 相同时候的情况。

所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1的,即:一个字符的回文子序列长度就是1。

其他情况dp[i][j]初始为0就行,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 中dp[i][j]才不会被初始值覆盖。

vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for (int i = 0; i < s.size(); i++) dp[i][i] = 1;

4.确定遍历顺序

从递归公式中,可以看出,dp[i][j] 依赖于 dp[i + 1][j - 1]dp[i + 1][j]dp[i][j - 1],如图:
在这里插入图片描述所以遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的。

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

代码如下:

for (int i = s.size() - 1; 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]);
        }
    }
}

5.举例推导dp数组

输入s:“cbbd” 为例,dp数组状态如图:
在这里插入图片描述

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

以上分析完毕,C++代码如下:

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
        for (int i = s.size() - 1; 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];
    }
};

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

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

相关文章

【H2实践】之 SpringBoot 整合

目标 本文紧接上篇【H2实践】之认识 H2&#xff0c;上篇简单介绍了 H2 及其使用&#xff0c;本文继上篇&#xff0c;探索 H2 与 SpringBoot 的整合。 主要实现目标&#xff1a; 1、SpringBoot 连接 H2 内存数据库&#xff0c;并对数据库进行管理。 2、访问 H2 Console 客户端…

Pytest 自动化测试框架

Pytest和Unittest测试框架的区别&#xff1f; 如何区分这两者&#xff0c;很简单unittest作为官方的测试框架&#xff0c;在测试方面更加基础&#xff0c;并且可以再次基础上进行二次开发&#xff0c;同时在用法上格式会更加复杂&#xff1b;而pytest框架作为第三方框架&#x…

52癫痫发作预测的有效双自注意力残差网络

Effective dual self-attentional residual networks for epileptic seizure prediction 摘要 癫痫发作预测作为慢性脑疾病中最具挑战性的数据分析任务之一&#xff0c;引起了众多研究者的广泛关注。癫痫发作预测&#xff0c;可以在许多方面大大提高患者的生活质量&#xff0…

springboot项目配置序列化,反序列化器

介绍本文介绍在项目中时间类型、枚举类型的序列化和反序列化自定义的处理类&#xff0c;也可以使用注解。建议枚举都实现一个统一的接口&#xff0c;方便处理。我这定义了一个Dict接口。枚举类型注解处理这种方式比较灵活&#xff0c;可以让枚举按照自己的方式序列化&#xff0…

三种通用方法——惠普电脑硬盘数据恢复

随着当今数字时代的到来&#xff0c;人们对于计算机硬盘上存储的数据越来越重视。然而&#xff0c;电脑硬盘也时常会发生数据丢失的情况&#xff0c;这时候就需要进行数据恢复。惠普电脑是市面上比较常见的品牌之一&#xff0c;因此本文将从惠普电脑硬盘数据恢复的角度出发&…

【你不知道的事】JavaScript 中用一种更先进的方式进行深拷贝:structuredClone

你是否知道&#xff0c;JavaScript中有一种原生的方法来做对象的深拷贝? 本文我们要介绍的是 structuredClone 函数&#xff0c;它是内置在 JavaScript 运行时中的: const calendarEvent {title: "Builder.io Conf",date: new Date(123),attendees: ["Steve…

EXCEL技能点3-常用技能1

1 引用格式 公式中引用单元格或者区域时,引用的类型可分为以下三种: 绝对引用 相对引用 混合引用 在Excel里&#xff0c;每个单元格都有一个编码&#xff0c;就像人的身份证一样&#xff0c;在Excel里是按照行列进行编码&#xff0c;例如A1就是第一列的第一行。 那么我们想要引…

QEMU启动x86-Linux内核

目录QEMU简介linux启动流程我的环境安装QEMU软件包安装源码安装编译linux内核编译busybox制作initramfs使用QEMU启动linux内核简化命令参考QEMU简介 QEMU&#xff08;quick emulator&#xff09;是一个通用的、开源的硬件模拟器&#xff0c;可以模拟不同硬件架构&#xff08;如…

Docker入门 第四部分

Docker 的优势 Docker 相比于传统虚拟化方式具有更多的优势&#xff1a; Docker 启动快速属于秒级别。虚拟机通常需要几分钟去启动。 Docker 需要的资源更少。Docker 在操作系统级别进行虚拟化&#xff0c;Docker 容器和内核交互&#xff0c;几乎没有性能损耗&#xff0c;性能…

Windows安装Hadoop

当初搭建Hadoop、Hive、HBase、Flink等这些没有截图写文&#xff0c;今为分享特重装。下载Hadoop下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/以管理员身份运行cmd切换到所在目录执行start winrar x -y hadoop-3.3.4.tar.gz&#xff0c;解压。配置…

刚刚和ChatGPT聊了聊隐私计算

开放隐私计算ChatGPT最近太火了&#xff0c;作为一个背后有庞大数据支撑&#xff0c;而且还在不断进化的人工智能&#xff0c;每个人都想和它聊一聊。我们也不例外&#xff0c;于是刚刚和它聊了聊隐私计算那些事儿。先来几个行业问题&#xff0c;毕竟它背后有所有行业新闻、论文…

Python tkinter 如何实现网站下载工具?将所有数据一键获取

前言 铁汁们有没有想过&#xff0c;如何把几个代码的功能结合到一起呢&#xff1f; 有想过的话&#xff0c;有没有实现过呢&#xff1f; 其实很简单的啊&#xff0c;咱就写一个界面就好了&#xff0c;想要哪个代码运行&#xff0c;鼠标轻轻一点就行 开发环境 python 3.8: 解…

【分布式版本控制系统Git】| Git概述、Git安装、Git常用命令

目录 一&#xff1a;概述 1.1. 何为版本控制 1.2. 为什么需要版本控制 1.3. 版本控制工具 1.4. Git 简史 1.5. Git 工作机制 1.6. Git和代码托管中心 二&#xff1a;安装 2.1. Git安装 三&#xff1a;常用命令 3.1 设置用户签名 3.2 初始化本地库 3.3 查看本地库…

多节点部署一键启动脚本

1 背景 我们在实际项目开发的时候,往往一个大的项目需要很多人协同开发,大家都开发完成在一起联调的时候需要启动很多节点,如果每次都手动去启动所有节点效率非常低,所以我们可以写一个脚本一键启动所有进程,以下统称为节点。 根据节点依赖的环境,一键启动脚本分两种情…

电力电子中逐波限流控制以及dsp实现

逐波限流是指在电力系统运行中&#xff0c;对电力设备进行电流保护的一种措施。它的实现方式是通过对电力系统的电流进行逐波监测和控制&#xff0c;每一波电流都可以独立地进行限制&#xff0c;从而保护电力系统设备不受过载损坏或短路故障的影响。 逐波限流的作用是提高电力…

1. ELK Stack 理论篇之什么是ELK Stack?

ELK Stack 理论篇之什么是ELK Stack?1.1 什么是 ELK Stack&#xff1f;1.2 ELK Stack的发展史1.2.1 Elasticsearch1.2.2 引入 Logstash 和 Kibana&#xff0c;产品更强大1.2.3 社区越来越壮大&#xff0c;用例越来越丰富1.2.4 然后我们向 ELK 中加入了 Beats1.2.5 那么&#x…

es6模块

目录 一、语法说明 二、示例代码 三、运行测试 一.语法说明 JavaScript 现在有两种模块。一种是 ES6 模块&#xff0c;简称 ESM&#xff1b;另一种是 CommonJS 模块&#xff0c;简称 CJS。CommonJS 模块是 Node.js 专用的&#xff0c;与 ES6 模块不兼容。语法上面&#xff0…

XE开发Linux应用(二)-Webservice

新建一个工程。选择如图。继续输入服务名然后就生成对应的单元。增加linux 平台。完善对应的单元代码{ Invokable implementation File for Txaliontest which implements Ixaliontest }unit xaliontestImpl;interfaceuses Soap.InvokeRegistry, System.Types, Soap.XSBuiltIns…

Hive常用内置函数、窗口函数及自定义函数

文章目录一、字符串函数二、数值函数三、日期函数四、其余常用函数五、窗口函数5.1、语法5.2、常用窗口函数六、自定义函数6.1、自定义UDF函数6.2、自定义UDTF函数6.3、将自定义函数导入hive中6.3.1、将项目打成jar包6.3.2、将jar包传入hive目录6.3.3、在hive中加载jar包6.3.4、…

C++回顾(二十五)—— map/multimap容器

25.1 map/multimap的简介 map是标准的关联式容器&#xff0c;一个map是一个键值对序列&#xff0c;即(key,value)对。它提供基于key的快速检索能力。map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入&#xff0c;所以不能指定插入位置。map的…