动态规划之回文串问题

回文串

  • 1. 回文子串
  • 2. 最长回文子串
  • 3. 分割回文串 IV
  • 4. 分割回文串 II
  • 5. 最长回文子序列
  • 6. 让字符串成为回⽂串的最⼩插⼊次数

1. 回文子串

1.题目链接:回文子串
2.题目描述:
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

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

示例 2:

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

提示:

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

3.问题分析:动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。 所以这道题还是寻找相同子问题,然后求解。对于在字符串中寻找回文子串,在字符串中可以先确定一个字符(字符串),然后在这个字符(字符串)的两侧扩展字符,如果扩展前的字符(字符串)是一个回文串,并且扩展的左右两侧的字符是相同的,那么扩展后的字符串就是一个回文串。这道题所求的是回文子串的数目,所以需要将这个字符串中的所以子串都枚举出来,寻找符合条件的数目。所以可以用一个二维dp表来表示以 i 为起始,以 j 为结尾的字符串是不是回文串,如果是回文串,那么就给返回值的结果加1。

  1. 状态表示:如上述分析用一个二维布尔类型的dp表来表示以 i 为起始,以 j 为结尾的字符串是不是回文串
  2. 状态转移方程:用两个for循环遍历s字符串,寻找子回文串,要判断以 i 为起始,以 j 为结尾的字符串是不是回文串,如上述所分析,对于中间元素来说,左右两边的元素都相同那么该字符串就为回文串;首先 i 位置的元素要等于 j 位置的元素,然后再判断以 i + 1为起始,以 j - 1为结尾的字符串是不是回文串,这样一个问题就被拆分成子问题。状态转移方程为:dp[i][j] = dp[i + 1][j - 1];还需要注意对临界情况分析,因为到最后的子问题可能会有多种情况:s可能为某一个字符串中的子串,i,j 为指向s起始和结尾的下标,假设1,s的长度为1个,i + 1,j - 1当作越界,但s是一个回文串;假设2,s的长度为2,并且 s[i] = s[j],i + 1,j - 1也越界,s也是一个回文串;假设3,s的长度为3,并且 s[i] = s[j],i+1,j-1后刚好接到假设1;假设4,s的长度为4,i+1,j-1后接到假设2,所以需要对长度为1和2的子串进行判断。 即当子串的长度为1或2时,若s[i]=s[j],则dp[i][j] = true。
  3. 初始化:dp表中全初始化为false。
  4. 填表顺序:由状态转移方程dp[i][j] = dp[i + 1][j - 1]可知,要求 i,j 位置需要知道 i + 1,j - 1位置的dp,所以需要从下往上,从做往右初始化。
  5. 返回值:返回dp表中所有为true的个数。

4.代码如下:

class Solution{
public:int countSubstrings(string s) {int n = s.size();int ret = 0;vector<vector<bool>> dp(n, vector<bool>(n));for (int i = n - 1; i >= 0; --i) //i从下往上填写{for (int j = i; j < n; ++j) //j从左往右填写{if (s[i] == s[j]){if (j - i < 2)dp[i][j] = true;elsedp[i][j] = dp[i + 1][j - 1];}//或者如下代码//if (s[i] == s[j])//    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;if (dp[i][j])++ret;}}return ret;}
};

2. 最长回文子串

1.题目链接:最长回文子串
2.题目描述:
给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

示例 2:

输入:s = “cbbd”
输出:“bb”

提示:

1 <= s.length <= 1000
s 仅由数字和英文字母组成

3.题目分析:这道题也是寻找所有的回文子串,但求的是回文子串的最长长度,所以当遇到一个回文子串时,需要计算回文子串的长度,然后保留最长的长度;在这里保存子字符串需要子字符串的起始,结尾位置,还需要保存子字符串的长度,再细分一下,只需要子字符串的起始位置和长度即可;在string类型中,有一个函数substr,string substr (size_t pos = 0, size_t len = npos) const;返回类型为string,所以只要知道最长子字符串的起始位置和长度即可。

  1. 状态表示:用一个二维布尔类型的dp表来表示以 i 为起始,以 j 为结尾的字符串是不是回文串
  2. 状态转移方程:dp[i][j] = dp[i + 1][j - 1].
  3. 初始化:dp表中全初始化为false。
  4. 填表顺序:由状态转移方程dp[i][j] = dp[i + 1][j - 1]可知,要求 i,j 位置需要知道 i + 1,j - 1位置的dp,所以需要从下往上,从做往右初始化。
  5. 返回值:返回s中的最长子字符串。

4.代码如下:

class Solution 
{
public:string longestPalindrome(string s) {int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));int begin = 0, length = 0; //记录最长子字符串的起始位置和长度for (int i = n - 1; i >= 0; --i){for (int j = i; j < n; ++j){if (s[i] == s[j]){dp[i][j] = j - i < 2 ? true : dp[i + 1][j - 1];if (dp[i][j]) //保留最长的回文子串{int l = j - i + 1;if (l > length){length = l;begin = i;}}}}}return s.substr(begin, length);}
};

3. 分割回文串 IV

1.题目链接:分割回文串 IV
2.题目描述:
给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。

当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。

示例 1:

输入:s = “abcbdd”
输出:true
解释:“abcbdd” = “a” + “bcb” + “dd”,三个子字符串都是回文的。

示例 2:

输入:s = “bcbddxy”
输出:false
解释:s 没办法被分割成 3 个回文子字符串。

提示:

3 <= s.length <= 2000
s​​​​​​ 只包含小写英文字母。

3.题目分析:这道题首先也是寻找s字符串中的回文子串,然后再在回文子串中寻找3个可以拼接s字符串的回文子串,具体如何寻找?寻找3个子串,i,j 位置表示的是由 i 到 j 为回文子串,那么i位置之前和j 位置之后都是回文子串,那么就可以返回true,这三个位置用dp[0][i - 1],dp[i][j],dp[j + 1][n - 1]表示,即这三个位置的bool值都为true,那么返回 true ,否则返回 false ;如果分割为四个子串该如何解决?五个子串呢?六个以及k个子串呢?这个思路用的应该是深度优先遍历,将每层为true的路径保存起来,(比如s的下标从0到2,4,6是回文串,1到…) 然后挨个遍历,寻找符合题意的路径即可,路径问题还没有深入学习,所以就先不写具体代码了。

4.代码如下:

class Solution
{
public:bool checkPartitioning(string s) {int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));for (int i = n - 1; i >= 0; --i){for (int j = i; j < n; ++j){if (s[i] == s[j])dp[i][j] = j - i < 2 ? true : dp[i + 1][j - 1];}}for (int i = 1; i < n - 1; ++i){for (int j = i; j < n - 1; ++j){if (dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1])return true;}}return false;}
};

4. 分割回文串 II

1.题目描述:分割回文串 II
2.题目描述:
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。

返回符合要求的 最少分割次数 。

示例 1:

输入:s = “aab”
输出:1
解释:只需一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串。

示例 2:

输入:s = “a”
输出:0

示例 3:

输入:s = “ab”
输出:1

提示:

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

3.题目分析:如上述几道题一样,先确定所有的回文子串,然后再回文子串中寻找最少能拼接s字符串的个数。在寻找最少能拼接s字符串的个数时,又是一个dp问题,如要寻找一个字符串中最小的分割次数,可以转为求字符串中某一个子串的最小的分割次数;所以定义一个f[i]:表示s 中[0, i] 区间上的字符串,最少分割的次数。然后一步一步分析:如果0~i位置是回文串,则f[i] = 0;如果不是,那么就在0到i中寻找回文子串,定义一个位置j,j到i如果是回文子串,那么0到i最小的分割次数就等于j - 1位置最小的分割次数加1,即f[i] = f[j - 1] + 1。 最后返回f表中最后一个位置的元素。

  1. 状态表示:dp[j] 表示: s 中[0, i]区间上的字符串,最少分割的次数。
  2. 状态转移方程:f[i] = min(f[i], f[j - 1] + 1)。
  3. 初始化:f表全初始化为最大值即可。
  4. 填表顺序:从左往右。
  5. 返回值:返回f表中最后一个位置的元素。

4.代码如下:

class Solution
{
public:int minCut(string s){int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));for (int i = n - 1; i >= 0; --i){for (int j = i; j < n; ++j){if (s[i] == s[j])dp[i][j] = j - i < 2 ? true : dp[i + 1][j - 1];}}vector<int> f(n, INT_MAX);for (int i = 0; i < n; ++i){if (dp[0][i]) //如果0~i是回文串,则不需要拆分f[i] = 0;else{//否则在j~i中寻找最小的分割次数for (int j = 0; j < i + 1; ++j){if (dp[j][i])f[i] = min(f[i], f[j - 1] + 1);}}}return f[n - 1];}
};

5. 最长回文子序列

1.题目链接:最长回文子序列
2.题目描述:
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

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

示例 1:

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

示例 2:

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

提示:

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

3.题目分析:这道题需要找到字符串中回文子序列,子序列不一定连续,遍历字符串肯定需要从某个位置到某个位置,所以定义dp[i][j]表示:s字符串 [i, j] 区间内的所有的⼦序列中,最⻓的回⽂⼦序列的⻓度;然后在i,j 位置进行分析,如果i = j,此时s[i] = s[j],则回文子序列长度为1,即dp[i][j] = 1。如果 i 不等于 j ,并且s[i] = s[j],那么i,j 位置的值是不是比i + 1,j - 1位置的长度多2,即dp[i][j] = dp[i + 1][j - 1] + 2;如果s[i] 不等于s[j],那么就在dp[i][j - 1],dp[i + 1][j]中找最大的值保留即可,为什么在dp[i][j - 1],dp[i + 1][j]中寻找?因为i,j不是回文串,(i,j-1),(i+1,j)中可能是回文串也可能不是,但是这两个位置存放的是最⻓的回⽂⼦序列的⻓度。因为是从下到上遍历,所以最后返回dp[0][n - 1]的值。

  1. 状态表示:dp[i][j]表示:s字符串 [i, j] 区间内的所有的⼦序列中,最⻓的回⽂⼦序列的⻓度。
  2. 状态转移方程:当 s[i] == s[j] 时: dp[i][j] = dp[i + 1][j - 1] + 2 ; 当s[i] != s[j] 时: dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])。
  3. 初始化:dp表全初始化为0,当 i = j 时,dp[i][j] = 1。
  4. 填表顺序:从下到上,从左往右。
  5. 返回值:返回dp[0][n - 1]的值。

4.代码如下:

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

6. 让字符串成为回⽂串的最⼩插⼊次数

1.题目链接:让字符串成为回⽂串的最⼩插⼊次数
2.题目描述:
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数 。

「回文串」是正读和反读都相同的字符串。

示例 1:

输入:s = “zzazz”
输出:0
解释:字符串 “zzazz” 已经是回文串了,所以不需要做任何插入操作。

示例 2:

输入:s = “mbadm”
输出:2
解释:字符串可变为 “mbdadbm” 或者 “mdbabdm” 。

示例 3:

输入:s = “leetcode”
输出:5
解释:插入 5 个字符后字符串变为 “leetcodocteel” 。

提示:

1 <= s.length <= 500
s 中所有字符都是小写字母。

3.问题分析:这道题也是从 i 位置到 j 位置进行分析,先定义一个dp[i][j]表示:字符串 [i, j] 区域成为回⽂⼦串的最少插⼊次数。然后对i,j 位置进行分析,如果s[i] = s[j],那么 [i, j] 区间内成为回⽂⼦串的最少插⼊次数,取决于 [i + 1, j - 1] 区间内成为回⽂⼦串的最少插⼊次数; 若 i = j 或 i = j - 1 ,那么 [i, j] 区间⼀定是回⽂⼦串,成为回⽂⼦串的最少插⼊次数是0。此时 dp[i][j] = i >= j - 1 ? 0 : dp[i + 1][j - 1] 。如果s[i] != s[j],那么就如上道题一样,在区间(i,j-1),(i+1,j)中寻找插入次数最小的,此时dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1由状态转移方程可知,要求dp[i][j],先要知道dp[i+1][j] 和dp[i][j-1]的值,所以对于i来说,应该从下到上遍历,对于j来说,应该从左往右遍历;因为是从下到上遍历,所以最后返回dp[0][n - 1]的值。

  1. 状态表示:dp[i][j]表示:字符串 [i, j] 区域成为回⽂⼦串的最少插⼊次数。
  2. 状态转移方程:如果 s[i] == s[j] : dp[i][j] = i >= j - 1 ? 1 : dp[i + 1][j - 1] ;如果 s[i] != s[j] : dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1 。
  3. 初始化:dp表全初始化为0。
  4. 填表顺序:从下到上,从左往右。
  5. 返回值:返回dp[0][n - 1]的值。

4.代码如下:

class Solution {
public:int minInsertions(string s) {int n = s.size();vector<vector<int>> dp(n, vector<int>(n));for (int i = n - 1; i >= 0; --i){for (int j = i; j < n; ++j){if (s[i] == s[j]){dp[i][j] = j - i < 2 ? 0 : dp[i + 1][j - 1];}else{dp[i][j] = min(dp[i][j - 1], dp[i + 1][j]) + 1;}}}return dp[0][n - 1];}
};

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

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

相关文章

多目标优化算法:基于非支配排序的鱼鹰优化算法(NSOOA)MATLAB

一、鱼鹰优化算法 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕食行为。 Python&#xff1a;鱼鹰优化算法&#xff08;Osprey optimization algorithm&a…

新版发布 | Cloudpods v3.10.5 和 v3.9.13 正式发布

Cloudpods v3.10.5 本期发布中&#xff0c;ocboot 部署脚本有较多变化&#xff0c;首先支持以非 root 用户执行安装流程&#xff0c;其次响应社区的呼吁&#xff0c;增加了–stack 参数&#xff0c;允许 Allinone 一键安装仅包含私有云&#xff08;参数为 edge&#xff09;或云…

ESP8266 WiFi物联网智能插座—项目简介

目录 1、项目背景 2、设备节点功能 3、上位机功能 物联网虽然能够使家居设备和系统实现自动化、智能化管理&#xff0c;但是依然需要依靠更为先进的终端插座作为根本保障&#xff0c;插座是所有家用电器需要使用的电源设备&#xff0c;插座的有序智能管理&#xff0c;对于实…

服务器免密登录设置

例如服务器A想要免密连接服务器B&#xff0c;需要以下2个步骤 步骤1&#xff1a;在服务器A上执行命令ssh-keygen –t rsa&#xff0c;直接回车&#xff0c;会在默认路径/root/.ssh下生成私钥和公钥 步骤2&#xff1a;将服务器A上生成的公钥id_rsa.pub的内容&#xff0c;复制粘…

进程的管理

#include <unistd.h> void _exit(int status); #include <stdlib.h> void _Exit(int status); status参数&#xff1a;是进程退出时的状态信息&#xff0c;父进程在回收子进程资源的时候可以获取到 #include<stdio.h> #include<stdlib.h> #includ…

【C++】搜索二叉树底层实现

目录 一&#xff0c;概念 二&#xff0c;实现分析 1. 插入 &#xff08;1.&#xff09;非递归版本 &#xff08;2.&#xff09;递归版本 2. 打印搜索二叉树 3.查找函数 &#xff08;1.&#xff09;非递归版本 &#xff08;2.&#xff09;递归版本 4. 删除函数&#x…

flex弹性盒模型与阿里图标的使用

华子目录 flex布局flex布局原理flex使用三要素 阿里图标&#xff08;字体&#xff09; flex布局 相关学习网站&#xff1a;http://c.biancheng.net/css3/flex.html 1.flex是当前最主流的布局方式&#xff1a;用它布局起来更方便&#xff0c;取代了浮动的作用。 2.浮动布局有缺…

Redis混合模式持久化原理

前言 前面文章中我们也介绍过Redis的持久化方式有两种&#xff1a;rdb持久化和aof持久化&#xff0c;具体详情可查看之前文章redis持久化。rdb持久化还是aof持久化它们都有各自的缺点。 rdb和aof缺点 rdb持久化&#xff1a;由于是定期对内存数据快照进行持久化&#xff0c;因此…

宝塔重装注意事项

欢迎关注我的公众号&#xff1a;夜说猫&#xff0c;让一个贫穷的程序员不靠打代码也能吃饭~ 前言 宝塔8.0版本&#xff0c;宝塔卸载重装&#xff0c;或者重装Linux系统后重新安装宝塔也适用。 不能上来直接就执行安装宝塔脚本&#xff0c;除非之前没有安装过宝塔。 步骤 1、…

【Mysql主从配置方法---单主从】

Mysql主从 主服务器 创建用户 create user “for_rep”“从服务器IP地址” IDENTIFIED by “123456” 授权 grant replication slave on . to “for_rep”“从服务器IP地址” IDENTIFIED by “123456” 查看用户权限 SHOW GRANTS FOR “for_rep”“从服务器IP地址”; 修改M…

Flutter粒子生成演示

演示&#xff1a; 直接上代码&#xff1a; import dart:math; import dart:ui;import package:flutter/material.dart; import package:kq_flutter_widgets/widgets/chart/ex/extension.dart;class ParticleView extends StatefulWidget {const ParticleView({super.key});ove…

Vue 使用vue-cli构建SPA项目(超详细)

目录 一、什么是vue-cli 二&#xff0c;构建SPA项目 三、 运行SPA项目 前言&#xff1a; 在我们搭建SPA项目时候&#xff0c;我们必须去检查我们是否搭建好NodeJS环境 cmd窗口输入以下指令&#xff1a;去检查 node -v npm -v 一、什么是vue-cli Vue CLI&#xff08;Vu…

Qt/C++音视频开发53-本地摄像头推流/桌面推流/文件推流/监控推流等

一、前言 编写这个推流程序&#xff0c;最开始设计的时候是用视频文件推流&#xff0c;后面陆续增加了监控摄像头推流&#xff08;其实就是rtsp视频流&#xff09;、网络电台和视频推流&#xff08;一般是rtmp或者http开头m3u8结尾的视频流&#xff09;、本地摄像头推流&#…

短视频矩阵系统,短视频矩阵源码技术开发

开发短视频矩阵系统的源码需要以下步骤&#xff1a; 确定系统需求&#xff1a;根据客户的需求&#xff0c;确定系统的功能和特点&#xff0c;例如用户注册登录、视频上传、视频浏览、评论点赞等。 设计系统架构&#xff1a;根据系统需求&#xff0c;设计系统的整体架构&#x…

前端工程师路上的宝藏:不可错过的进阶必读文章!

JavaScript 《javascript高级程序设计》核心知识总结 必要性&#xff1a;⭐️⭐️⭐️⭐️ 难度&#xff1a;⭐️⭐️⭐️⭐️ 谏言&#xff1a;建议初学者先读一两遍红宝石书&#xff08;即JavaScript高级程序设计&#xff09;&#xff0c;犀牛书可以暂时不看&#xff08;…

csp初赛总结 那些年编程走过的坑 初高中信竞常考语法算法点

&#x1f618;个人主页&#xff1a;曲终酣兴晚的小书屋&#x1f496; &#x1f615;作者介绍&#xff1a;一个莽莽撞撞的&#x1f43b; &#x1f496;专栏介绍&#xff1a;日常生活&往事回忆 &#x1f636;‍&#x1f32b;️每日金句&#xff1a;祝大家心有山水不造作&…

typedef function<int (int,int)> func_t;

这段代码是C中用于创建函数类型别名&#xff08;function type alias&#xff09;的语法。让我们来逐步解释它&#xff1a; typedef: typedef 是C中的关键字&#xff0c;用于创建类型别名。它允许你为一个已存在的类型创建一个新的、易于使用的名称。 function: 这部分指定了要…

【Java 基础篇】Java同步代码块解决数据安全

多线程编程是现代应用程序开发中的常见需求&#xff0c;它可以提高程序的性能和响应能力。然而&#xff0c;多线程编程也带来了一个严重的问题&#xff1a;数据安全。在多线程环境下&#xff0c;多个线程同时访问和修改共享的数据可能导致数据不一致或损坏。为了解决这个问题&a…

【打印文件】python实现-附ChatGPT解析

1.题目 打印文件 时间限制: 1s 空间限制: 256MB 限定语言:不限 题目描述: 有5台打印机打印文件,每台打印机有自己的待打印队列。 因为打印的文件内容有轻重缓急之分,所以队列中的文件有1~10不同的优先级,其中数宁越大优先级越高。 打印机会从自己的待打印队列中选择优先级最…

【差旅游记】初见乌海湖

哈喽&#xff0c;大家好&#xff0c;我是雷工。 最近在乌海出差&#xff0c;有幸见到了传说中在沙漠中看海的“黄河明珠”——乌海湖。 前段时间一直有点忙&#xff0c;现在有点时间&#xff0c;趁还没忘光&#xff0c;简单整理记录下。 那是在上个月&#xff0c;2023年8月8号…