算法专题:子序列系列2(回文)

文章目录

    • 示例1:回文子串
      • 思路
        • DP数组含义(注意)
        • 递推公式
        • 初始化
        • 遍历顺序(注意)
      • 完整版
      • 注意点
    • 示例2:最长回文子序列
      • 思路
        • DP数组含义
        • 递推公式
        • 初始化
        • 遍历顺序
      • 完整版
      • 注意点
      • 另一种写法
        • 不能把DP数组全部初始化为1的原因

回文串系列,主要在于DP数组定义,与递推过程中的遍历顺序,与之前的子序列差别比较大。

示例1:回文子串

647. 回文子串 - 力扣(LeetCode)

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2:

输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成

思路

如果做了很多这种子序列相关的DP题目,在定义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数组含义(注意)

bool型dp[i][j]:表示如果起点为i,终点为j的区间,该区间的子串是回文串 (注意是左闭右闭),那么dp[i][j]为true,否则为false。

递推公式

判断区间[i,j]构成的子串是不是回文串,只有三种情况:

  • i=j,也就是本身的情况,一个字符本身是一个回文串,对应"a"的情况
  • j-i=1,也就是i和j相邻的情况,那么相邻的两个字符,构成一个回文串,也就是"aa"的情况
  • dp[i+1][j-1]是一个回文串,也就是”aba“的情况
if(s[j]==s[i]){if((j-i)<=1){//包括了j=i和j-i=1的情况,也就是相邻的情况和重合(只有一个)的情况result++;dp[i][j]=true;}else if(dp[i+1][j-1]){result++;//外层多了一个也是回文串,结果+1dp[i][j]=true;}
}

初始化

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

遍历顺序(注意)

注意,在本题中,是通过dp[i+1][j-1]来推出dp[i][j]的状态,因此i的状态要通过i+1来推出。因此i的遍历必须是倒序遍历

同时,j是从j-1状态推出来,因此j的遍历是正序遍历

for(int i=s.size();i>=0;i--){for(int j=i;j<s.size();j++){if(s[j]==s[i]){if((j-i)<=1){//字符本身,或者是相邻字符result++;dp[i][j]=true;}else if(dp[i+1][j-1]){//内部相等result++;dp[i][j]=true;}}}
}

完整版

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--){//倒序的时候i的范围是[0,s.size()-1]!for(int j=i;j<s.size();j++){if(s[j]==s[i]){if((j-i)<=1){//这里包含了两种情况,(j-i)=1的情况和j=i的情况result++;dp[i][j]=true;}else if(dp[i+1][j-1]){result++;//外层相等也是多了一个回文串dp[i][j]=true;}}}}return result;}
};

注意点

  • 倒序遍历的时候i的范围是[0,s.size()-1],也就是说倒序结束条件应该是i>=0
  • 回文串主要是情况分析。是回文的情况只有三种:
    • i=j,也就是本身的情况,一个字符本身是一个回文串,对应"a"的情况
    • j-i=1,也就是i和j相邻的情况,那么相邻的两个字符,构成一个回文串,也就是"aa"的情况
    • dp[i+1][j-1]是一个回文串,也就是”aba“的情况

分析完这三种之后再写递推,递推就比较好写了。

示例2:最长回文子序列

516. 最长回文子序列 - 力扣(LeetCode)

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成

思路

本题是不连续的,但是DP数组依然是按照回文系列定义[i,j]区间的方式来定义。

DP数组含义

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

递推公式

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

如果相同,那么在dp[i+1][j-1]的基础上**+2**。(此时还有j=i的情况,j=i应该+1)

如果不相同,那么就在不包含的两种情况(不包含s[i]或者不包含s[j])中选择最大值。

for(int i=s.size()-1;i>=0;i--){for(int j=i;j<s.size();j){if(s[i]==s[j]){if(i=j) dp[i][j]=1;//注意DP数组的定义,是区间[i,j]内回文子序列最大长度!else if(j>0)dp[i][j]=dp[i+1][j-1]+2;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}
}

初始化

vector<vector<int>>dp(s.size(),vector<int>(s.size(),0));

遍历顺序

遍历顺序同上一题,i是倒序遍历,j是正序遍历,且j>i(本题不是j>=i了,因为长度累加+2)

完整版

class Solution {
public:int longestPalindromeSubseq(string s) {vector<vector<int>>dp(s.size(),vector<int>(s.size(),0));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(i==j) dp[i][j]=1;//注意DP数组的定义,是区间[i,j]内回文子序列最大长度!else if(j>0)dp[i][j]=dp[i+1][j-1]+2;}else{dp[i][j]=max(dp[i+1][j],dp[i][j-1]);//一定要注意,这里是从[i+1,j]和[i,j-1]里面取值,因为i是倒序遍历,j是正序遍历}result=max(result,dp[i][j]);}}return result;}
};

注意点

  • 注意本题是求回文子串内部最大长度,而且不连续,因此本题j=ij!=i的情况就需要分开。
  • j=i的情况是长度+1,j!=is[j]==s[i]的情况,是长度+2。
  • 遍历顺序和不等情况下的取值也有关系,如果是不等的情况,那么去除当前取值应该在[i+1,j-1]的范围里面取

另一种写法

  • 也可以在初始化的时候,单独处理i=j的情况,令dp[i][i]=1
class Solution {
public:int longestPalindromeSubseq(string s) {//vector<vector<int>> dp(s.size(), vector<int>(s.size(), 1));vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));//不能用1来初始化二维数组,需要用0,为了避免s[i]!=s[j]但是dp[i][j]=1的情况for(int i=0;i<s.size();i++){dp[i][i]=1;}for (int i = s.size() - 1; i >= 0; i--) {//j直接从i+1开始,只处理+2的情况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];}
};

不能把DP数组全部初始化为1的原因

dp[i][j]表示从索引i到索引j的字符串的最长回文子序列的长度

如果字符串s[i]s[j]不相等,且j=i+1,如果直接让dp[i][j]的值为1,但实际上,因为s[i]和s[j]不相等,所以它们不可能构成一个回文子序列,dp[i][j]应当为0

dp[i][j]应该被初始化为0,因为在默认情况下,如果没有找到任何回文子序列,那么回文子序列的长度应该为0。只有当i等于j时,也就是只有一个字符的时候,我们才把dp[i][j]设置为1,因为单个字符自身就是一个长度为1的回文子序列。

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

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

相关文章

【pandas使用小技巧】如何将DataFrame的index有序化【reset_index()方法】

在pandas中&#xff0c;可以使用**reset_index()**方法将DataFrame的index改成连续的。reset_index()方法会将原来的index作为一列新的数据添加到DataFrame中&#xff0c;并将新的index设置为连续的整数。栗子如下&#xff0c;演示如何使用reset_index()方法将DataFrame的index…

vue3搭建(vite+create-vue)

目录 前提条件 输入命令 对于Add an End-to-End Testing Solution nightwatch和Cypress 和 Playwright 运行 前提条件 熟悉命令行已安装 16.0 或更高版本的 Node.js &#xff08;node -v查看版本&#xff09; 输入命令 npm init vuelatest 这一指令将会安装并执行 create-…

Meta AI研究团队新AI模型: Llama 2 大语言模型

Llama是Facebook Research团队开发的基础语言模型集&#xff0c;旨在提供广泛的语言理解能力。它基于转换器架构&#xff0c;参数范围从7B到65B。通过使用Llama模型&#xff0c;研究人员和开发人员可以构建更先进的自然语言处理系统。您可以在GitHub上找到相关的代码和资源&…

kotlin 编写一个简单的天气预报app(四)增加界面显示

编写界面来显示返回的数据 用户友好性&#xff1a;通过界面设计和用户体验优化&#xff0c;可以使天气信息更易读、易理解和易操作。有效的界面设计可以提高用户满意度并提供更好的交互体验。 增加城市名字的TextView <TextViewandroid:id"id/textViewCityName"…

web基础与http协议

dns域名&#xff1a;网络是基于tcp/ip协议进行通信和连接的 ip地址&#xff1a;每一台主机都是有一个唯一的地址标识&#xff08;固定的ip地址&#xff09; 作用&#xff1a;1、区分用户和计算机 2、通信 ip地址的问题&#xff1a;由32位二进制数组成&#xff0c;不方便记忆…

CentOS 7虚拟机 虚拟机安装安装增强VBox_GAs_6.1.22失败:modprobe vboxguest failed

我安装的CentOS 在安装增强工具的时候报错: 查阅资料后 &#xff0c;解决方法&#xff1a; 1、更新kernel内核版本&#xff1a; yum update kernel -y //安装kernel-devel和gcc编译工具链yum install -y kernel-devel gcc//更新kernel和kernel-devel到最新版本yum -y upgrade …

就业并想要长期发展选数字后端还是ic验证?

“就业并想要长期发展选数字后端还是ic验证&#xff1f;” 这是知乎上的一个热点问题&#xff0c;浏览量达到了13,183。看来有不少同学对这个问题感到疑惑。之前更新了数字后端&数字验证的诸多文章&#xff0c;从学习到职业发展&#xff0c;都写过&#xff0c;唯一没有做过…

windows编译ncnn

官方代码https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-windows-x64-using-visual-studio-community-2017 编译工具 visual studio 2017 一、编译protobuf 1、下载protobuf protobuf-3.11.2&#xff1a;https://github.com/google/protobuf/archive/v3.11…

指针初阶(超详解)

指针初阶 1.指针是什么2.指针和指针类型2.1 指针-整数2.2 指针的解引用 3.野指针3.1 野指针成因3.2如何避免野指针 4.指针运算4.1 指针-整数4.2 指针-指针4.3 指针的关系运算 5.指针和数组6.二级指针7.指针数组 1.指针是什么 指针是什么&#xff1f; 指针理解的2个要点&#xf…

Class.forName和ClassLoader区别

【反射中&#xff0c;Class.forName和ClassLoader区别】_lfsun666的博客-CSDN博客

docker启动报错:Cannot connect to the Docker daemon

Couldn’t connect to Docker daemon at httpdocker://localunixsocket - is it running? 解决方式&#xff1a;正确的是将当前用户加入docker组解决步骤 sudo groupadd docker #添加docker用户组 sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中 ne…

SDWAN的技术背景及其发展历程

一、SDWAN&#xff08;软件定义广域网&#xff09;技术的背景 传统广域网&#xff08;WAN&#xff09;主要基于专线连接&#xff0c;例如MPLS&#xff08;多协议标签交换&#xff09;网络&#xff0c;用于连接企业的分支机构和总部。这些传统的WAN网络在过去是为了满足数据中心…

BladeX框架开源-工作-笔记-Docker部署-Jenkins配置

BladeX框架开源-工作-笔记-Docker部署-Jenkins配置 文章目录 BladeX框架开源-工作-笔记-Docker部署-Jenkins配置第一章-概要-BladeX框架简介与git地址第二章-BladeX框架前后端项目Docker部署与DockerFile配置文件2.1-开始部署阶段&#xff0c;默认服务器上面已有Nacos服务2.2-采…

maven下载按照及初次使用相关配置

maven下载按照及初次使用相关配置 一、下载 与安装 依赖Java&#xff0c;需要配置JAVA_HOME设置MAVEN自身的运行环境&#xff0c;需要配置MAVEN_HOME测试环境配置结果 MVN测试成功&#xff01;&#xff01;&#xff01; 二、本地仓库配置 Maven启动后&#xff0c;会自动保…

基于Amoeba读写分离(三十六)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 今天要学的是基于Amoeba读写分离。Amoeba是一个开源的关系型数据库管理系统&#xf…

Spark性能调优之数据序列化

前言 在使用Spark进行数据开发的时候,避不开的一个问题就是性能调优。网上一搜一大堆所谓的调优策略很多作者自己都不知所云,导致读者看了后只会更加困惑。我们在研究一个技术的时候第一手资料永远都请参考官网,官网对性能优化不一定是最全甚至最优,但是可以解决大部分问题…

六、初始化和清理(4)

本章概要 数组的初始化 动态数组创建可变参数列表 枚举类型 数组初始化 数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 [] 来定义和使用的。要定义一个数组引用&#xff0c;只需要在类型名加上方括号&#xf…

NestJS 的 拦截器 学习

拦截器会用到RxJs&#xff0c;所以在学习拦截器之前可以先了解一下它。 拦截器是使用Injectable()装饰器装饰的类并且实现了接口NestInterceptor。 拦截器受到 AOP(面向切面编程)技术的启发&#xff0c;具有如下的功能&#xff1a; 在方法执行之前/之后绑定额外的逻辑转换函…

express学习笔记2 - 三大件概念

中间件 中间件是一个函数&#xff0c;在请求和响应周期中被顺序调用&#xff08;WARNING&#xff1a;提示&#xff1a;中间件需要在响应结束前被调用&#xff09; 路由 应用如何响应请求的一种规则 响应 / 路径的 get 请求&#xff1a; app.get(/, function(req, res) {res…

以beam search为例,详解transformers中generate方法(上)

以beam search为例&#xff0c;详解transformers中generate方法&#xff08;上&#xff09; 1. generate的代码位置2. GenerationMixin概览3. generate签名4. generate过程4.1 读取并更新generation config4.2 补充没有传入的参数4.3 定义模型输入4.4 定义模型的其他参数4.5 对…