代码随想录57——动态规划:647回文子串、516最长回文子序列

news2025/1/18 8:22:03

文章目录

  • 1.647回文子串
    • 1.1.题目
    • 1.2.解答
  • 2.516最长回文子序列
    • 2.1.题目
    • 2.2.解答

1.647回文子串

参考:代码随想录,647回文子串;力扣题目链接

1.1.题目

在这里插入图片描述

1.2.解答

动规五部曲

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

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

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

2.确定递推公式

在确定递推公式时,就要分析如下几种情况。

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

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

(2) 当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况

  • 情况一:下标ij 相同,同一个字符例如 a,当然是回文子串
  • 情况二:下标ij 相差为1,例如 aa,也是回文子串
  • 情况三:下标:i j相差大于1的时候,例如 cabac,此时s[i]s[j]已经相同了,我们看 ij 区间是不是回文子串就看 aba 是不是回文就可以了,那么aba的区间就是 i+1 j-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]都是经过计算的。

代码如下:

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个回文子串

最后给出代码如下,注意其中有很多赋值false的地方都是可以省略的,因为dp数组初始化都是false。但是这里为了逻辑的完备性方便理解,把所有的状态都写进去了:

int countSubstrings(string s)
{
    // 1.定义dp数组并初始化
    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++)
        {
            // 1.如果当前起始字符和结束字符不相等,那么肯定就不是回文了
            if(s[i] != s[j])   
                dp[i][j] = false;
            else
            {
                // 2.否则如果相等,则判断字符长度:
                //  2.1.如果是1或2,比如a或aa的情况,自然是回文
                if(j - i <= 1)
                {
                    result++;   // 总的回文字符个数+1
                    dp[i][j] = true;  // 标记当前开始和结束位置构成的字符串是回文串
                }
                //  2.2.如果长度超过2,则需要判断各自往里收缩一个字符得到的子串是否是回文串,
                //      这里就用到了动态规划:即当前位置的状态取决于上一个位置的状态
                else 
                {
                    if(dp[i+1][j-1])    // 内部的子串是回文串
                    {
                        result++;   
                        dp[i][j] = true;
                    }
                    else    // 内部子串不是回文串
                    { 
                        dp[i][j] = false;
                    }
                }

            }
            
        }
    }

    return result;
}

2.516最长回文子序列

参考:代码随想录,516最长回文子序列;力扣题目链接

2.1.题目

在这里插入图片描述

2.2.解答

我们刚刚做过了 动态规划:回文子串,求的是回文子串,而本题要求的是回文子序列, 要搞清楚这两者之间的区别。

回文子串是要连续的,回文子序列可不是连续的! 回文子串,回文子序列都是动态规划经典题目。

思路其实是差不多的,但本题要比求回文子串简单一点,因为情况少了一点。

动规五部曲分析如下:

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

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

2.确定递推公式

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

(1) 如果s[i]s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;,如图:
在这里插入图片描述
(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; 可以看出 递推公式是计算不到 i 和j相同时候的情况

所以需要手动初始化一下,当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]才不会被初始值覆盖,因为我们取得是序列的最大长度,肯定是>=0的,所以初始化成0的话max不会被初始值覆盖。

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] + 2 dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) 可以看出,dp[i][j]是依赖于dp[i + 1][j - 1] dp[i + 1][j]

也就是从矩阵的角度来说,dp[i][j] 下一行的数据。 所以遍历i的时候一定要从下到上遍历,这样才能保证,下一行的数据是经过计算的。而**j是依赖于j-1的,所以遍历j的时候一定要从左往右遍历**。

递推公式:dp[i][j] = dp[i + 1][j - 1] + 2,dp[i][j] = max(dp[i + 1][j], dp[i][j - 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]);
        }
    }
}

5.举例推导dp数组

输入s:"cbbd" 为例,dp数组状态如图:

在这里插入图片描述
红色框即:dp[0][s.size() - 1]; 为最终结果。

最后给出代码如下:

int longestPalindromeSubseq(string s)
{
    // 1.定义dp数组,并部分初始化
    vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));

    // 2.初始化dp数组
    for(int i = 0; i < s.size(); i++)
        dp[i][i] = 1;

    // 3.动态规划:开始递归
    for(int i = s.size()-1; i >= 0; i--)
    {
        for(int j = i + 1; j < s.size(); j++)
        {
            // 1.当前首尾相等,则最长回文序列长度 = 内部子串的最长回文序列长度 + 2(即当前前后两个字符)
            if(s[i] == s[j])
                dp[i][j] = dp[i+1][j-1] + 2;
            // 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/10002.html

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

相关文章

如何优雅的使用 IDEA Debug 进行调试

如何优雅的使用 IDEA Debug 进行调试 Debug 是我们在开发过程中经常会使用到的一种排查问题的手段&#xff0c;我们用它来定位分析异常的出现&#xff0c;以及程序在运行中参数的变化。 IDEA 本身具有很强的调试功能&#xff0c;掌握 IDEA 的一些 Debug 技巧&#xff0c;对我们…

【JS】原生js实现矩形框的绘制/拖动/拉伸

1、要点及功能描述 通过js监听mouse事件来实现矩形框的绘制&#xff0c;再通过区分点击的是边角还是其他位置来实现矩形框的拉伸和拖动&#xff0c;并且在拖动和拉伸时&#xff0c;都做了边界限制&#xff0c;当拉伸或拖动 到边界时&#xff0c;就不能继续拉伸拖动了。当然在相…

7个实用有效的shopify运营策略,跨境电商卖家必知

关键词&#xff1a;shopify运营、跨境电商卖家 您的Shopify 在线商店是使用当今最好的平台之一构建的。2022 年第二季度&#xff0c;Shopify 在美国电子商务平台中占据最大市场份额&#xff0c;约占美国所有在线业务的 32%。 这也意味着电子商务品牌之间的竞争比以往任何时候都…

【图像融合】基于matlab DSIFT多聚焦图像融合【含Matlab源码 2224期】

⛄一、SIFT配准简介 1 算法概述 在实时系统中&#xff0c;算法的输入为相机数据流&#xff0c;当前输入的图像与上一张相似度很高时应不参与融合&#xff0c;由于在体视显微镜下序列图像存在较大程度的偏移&#xff0c;所以融合前还需要进行图像配准&#xff0c;配准完成后再进…

安杰思提交注册:预计2022年度收入不低于3.5亿元,同比增长15%

11月16日&#xff0c;杭州安杰思医学科技股份有限公司&#xff08;下称“安杰思”&#xff09;在上海证券交易所科创板提交招股书&#xff08;注册稿&#xff09;。据贝多财经了解&#xff0c;安杰思于2022年6月24日在科创板递交上市申请材料&#xff0c;2022年11月7日获得上市…

面试--线程池的执行流程和拒绝策略有哪些?

一. 执行流程 聊到线程池就一定会聊到线程池的执行流程, 也就是当有一个任务进入线程池之后, 线程池是如何执行的? 想要真正的了解线程池的执行流程&#xff0c;就得先从线程池的执行方法 execute() 说起, execute() 实现源码如下: public void execute(Runnable command)…

2.10.2版本的青龙升级2.10.13及2.11.3版本的教程

重要提醒&#xff1a; 这个教程仅限使用我下面这个命令搭建的青龙面板使用 docker run -dit \--name QL \--hostname QL \--restart always \-p 5700:5700 \-v $PWD/QL/config:/ql/config \-v $PWD/QL/log:/ql/log \-v $PWD/QL/db:/ql/db \-v $PWD/QL/scripts:/ql/scripts \-…

【消息队列笔记】chp3-如何确保消息不丢失

一、检测消息是否丢失 我们要保证消息的可靠交付&#xff0c;首先就要知道消息是否丢失了。如何做到这一点呢&#xff1f; 对于IT基础设施比较完善的公司&#xff0c;可以使用分布式链路追踪系统来追踪每一条消息。如果没有这样的系统&#xff0c;可以使用消息的有序性来验证…

圆角矩形不是圆:圆角的画法和二阶连续性

本文中的所有重要图片都会给出基于Matplotlib的Python绘制代码以供参考 引言 如果在百度搜索圆角矩形的画法&#xff0c;那么多数结果都会告诉你&#xff0c;就是把一个普通矩形的拐角换成相切的 14\frac{1}{4}41​ 圆弧&#xff0c;就像 引文1 和 引文2 说的那样。然而&#x…

网络规划设计与综合布线技术详解

一、网络工程概述 1、计算机网络及其组成 计算机网络是现代通信技术与计算机技术相结合的产物。 随着计算机网络本身的发展,人们认为:计算机网络是把地理位置不同、功能独立自治的计算机系统及数据设备通过通信设备和线路连接起来,在功能完善的网络软件运行支持下,以实现…

springboot+vue实现excel的导出

首先是springboot对数据的处理 依赖的导入 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency>RequestMapping("/exportExcel") public R exportR…

【SpringBoot项目】SpringBoot项目-瑞吉外卖【day01】

文章目录前言软件开发整体介绍软件开发流程瑞吉外卖项目介绍项目介绍产品原型展示技术选型功能架构角色开发环境搭建数据库环境搭建maven项目搭建设置静态资源映射后台登录需求分析代码开发功能测试后台退出需求分析代码开发功能测试&#x1f315;博客x主页&#xff1a;己不由心…

JVS低代码如何实现复杂物料编码?

日常业务过程中&#xff0c;存在大量的编码&#xff0c;例如订单的流水号&#xff0c;复杂的物料编码&#xff0c;学生证号等等场景&#xff0c;那么通过JVS如何去实现各种编码&#xff1f; 为了让使用者使用尽量简单&#xff0c;我们编码分为简单配置的编码和复杂配置的编码。…

[附源码]java毕业设计家校通信息管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

docker stats查询容器状态显示异常有模线

docker stats 命令用来显示容器使用的系统资源。 默认情况下&#xff0c;stats 命令会每隔 1 秒钟刷新一次输出的内容直到你按下 ctrl c。 输出详情介绍&#xff1a; CONTAINER ID 与 NAME: 容器 ID 与名称。 CPU % 与 MEM %: 容器使用的 CPU 和内存的百分比。 MEM USAGE…

Java基础之《netty(1)—netty介绍》

一、介绍 1、netty是由JBOSS提供的一个java开源框架&#xff0c;现为github上的独立项目。 2、netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;可以快速开发高性能、高可靠的网络IO程序。 3、netty主要针对在TCP协议下&#xff0c;面向clients端的高并发应用&…

【python3】4.文件管理

2022.11.16 本学习内容总结于莫烦python:4.文件管理 https://mofanpy.com/tutorials/python-basic/interactive-python/read-write-file4 文件管理 4.1 读写文件 均是用特殊字符open 4.1.1 创建文件 f open("new_file.txt", "w") # 创建并打开 f.wr…

进入数字化供应链高潮期,与IBM咨询共创无边界竞争力

供应链领域的国际专家马丁克里斯托弗在30年前就提出“未来的竞争不再是企业和企业之间的竞争&#xff0c;而是供应链之间的竞争。”近几年来&#xff0c;基于工业4.0技术的供应链4.0开始进入业界的视野&#xff0c;2020年开始的疫情让全球供应链结束了长期稳定状态而进入VUCA&a…

针对谷氨酰胺运输体的小分子抑制剂

精准癌症治疗手段 在运输体水平靶向谷氨酰胺代谢 ACST2 蛋白是谷氨酰胺进入癌细胞的主要转运蛋白。在肺癌、乳腺癌、结肠癌等癌症中&#xff0c;ACST2 蛋白水平的上调与患者存活率有关。当抑制 ACST2 基因表达时&#xff0c;会显著抑制癌细胞生长。ASCT2 水平升高与多种癌症的…

【支付】支付安全

这里写目录标题1. 加密 - 对称加密与非对称加密1.1 对称加密1.2 非对称加密1.3 对称加密与非对称加密区别2. 身份认证公钥加密&#xff0c;私钥解密&#xff08;加密信息&#xff09;私钥加密&#xff0c;公钥解密&#xff08;身份认证&#xff09;3. 摘要算法与数据完整性3.1 …