动态规划:回文串问题(C++)

news2025/2/26 18:16:22

动态规划:回文串问题

    • 前言
    • 回文串问题
      • 1.回文子串(中等)
      • 2.回文串分割IV(困难)
      • 3.分割回文串II(困难)
      • 4.最长回文子序列(中等)
      • 5.让字符串成为回文串的最小插入次数(困难)

前言

动态规划往期文章:

  1. 动态规划入门:斐波那契数列模型以及多状态
  2. 动态规划:路径和子数组问题
  3. 动态规划:子序列问题

回文串问题

1.回文子串(中等)

链接:回文子串

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    依据前面的经验,我们尝试定义状态表示:
    (1)以i位置为结尾的回文串个数。这个最好想到,但这道题很明显是不行的,因为只有以某一个位置为结尾的个数信息,无法推导出其它位置的状态表示。(不知道这些回文串的具体情况)

    (2)以i位置为结尾的回文串是否为真。只要组成位置不同就视为不同回文子串,因此确定真假然后累加就可以。但这个表示还是不足,第一点是以i位置为结尾的回文串可能有很多;第二点就是无法推导转移方程。(原因和(1)差不多)

    (3)上述表示不可行的主要原因就是没有利用回文串性质。对于[i , j]区间的字符串,如果s[i] == s[j]并且[i + 1, j - 1]区间为回文串,那[i, j]区间的字符串就是一个回文串。
    故我们需要一个二维表dp[i][j]:[i, j]区间的子串是否为回文串
    在这里插入图片描述

  2. 状态转移方程
    我们对[i, j]区间的子串进行分析:
    (1)s[i] != s[j],为假,dp[i][j] = false。

    (2)s[i] == s[j],这个分长度讨论:
    ①[i, j]区间长度为1,这个情况为真,dp[i][j] = true。
    ②[i, j]区间长度为2,这个情况也为真,dp[i][j] = true。
    ③[i, j]区间长度大于2(i + 1 < j),这个时候就要看[i - 1][j + 1]区间了,dp[i][j] = dp[i + 1][j - 1]。

  3. 初始化
    开始全都初始化为false

  4. 填表顺序
    填当前位置可能需要左下角的状态,故填表顺序为行从下到上,每一行从左到右

  5. 返回值
    需要返回 dp 表中 true 的个数,用变量ret统计即可。

  • 代码实现
class Solution {
public:
    int countSubstrings(string s)
    {
        int n = s.size();
        int ret = 0;
        //dp[i][j]:字符串[i, j]的⼦串,是否是回⽂串。
        vector<vector<bool>> dp(n, vector<bool>(n, false));

        for (int i = n - 1; i >= 0; i--)
        {
            for (int j = i; j < n; j++)
            {
                //子串长度为三:i + 1 < j
                if (s[j] == s[i])                  
                    dp[i][j] = (i + 1 < j ? dp[i + 1][j - 1] : true);
                if (dp[i][j])  ret++;  //累加个数                
            }
        }
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N ^ 2)
    }
};

2.回文串分割IV(困难)

链接:回文串分割IV

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 算法思路
    这个题目虽然标的是困难,但是有前面的基础其实还是比较容易的。
    我们可以把问题拆分出来:
    (1)先知道不同区间段的子串是否为回文串(第一题)
    (2)枚举所有分成三段的情况,其中有一个为真即真,否则为假。
    (把区间分成[0, i - 1],[i, j],[j + 1, n - 1]三段,枚举i与j即可)
  • 代码实现
class Solution {
public:
    bool checkPartitioning(string s) {
        int n = s.size();  
        //dp[i][j]:字符串[i, j]的⼦串,是否是回⽂串。
        vector<vector<bool>> dp(n, vector<bool>(n, false));
        
        for (int i = n - 1; i >= 0; i--)
            for (int j = i; j < n; j++)
                if (s[j] == s[i])               
                    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
                          
        //枚举判断能否分成三个
        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;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N ^ 2)
    }
};

3.分割回文串II(困难)

链接:分割回文串II

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    我们对问题进行拆分:
    (1)先知道不同区间段的子串是否为回文串(第一题)
    (2)依据之前的做题经验,可以定义状态表示为dp[i]:[0, i]区间的字符串,使每个子串都是回文的最小切割次数

  2. 状态转移方程
    从[0, n - 1]枚举i:
    (1)[0, i]直接就是回文串,需要的切割次数为0

    (2)[0, i]不是回文串:
    枚举j,如果[j, i]为回文串,只需要让[0, j - 1]区间的字符串每个子串都是回文串,然后在j这个位置切一刀就行。即dp[i] = dp[j - 1] + 1。
    但使[j, i]为回文串中满足条件的j可能有很多个,我们需要取其中的最小值
    例子:比如"aabb"这个例子:
    ①对于第一个b位置,前面aa是回文,需要切割的次数为0。要保持"aab"所有子串为回文,只能是b一个做回文,在这个位置切一刀,
    dp[i] = 0 + 1。(0为保持"aa"所有子串为回文的最小切割次数)
    ②对于第二个b位置,要保持"aabb"所有子串回文,有两种选择。
    i. 让"aa"所有子串保持回文,"bb"做回文,在这个位置切一刀,
    即dp[i] = 0 + 1 = 1(0为保持"aa"所有子串为回文的最小切割次数)
    ii. 让"aab"所有子串保持回文,b自己一个做回文,在这个位置切一刀,dp[i] = 1 + 1 = 2。(前面1为保持"aab"所有子串为回文的最小切割次数)。
    取两种选择中次数最小的一方即可。

  3. 初始化
    要多次取最小值,为避免默认0干扰结果,我们初始化为极大值

  4. 填表顺序
    填表顺序从左往右

  5. 返回值
    依据状态表示,返回值应该是dp[n - 1]。

  • 代码实现
class Solution {
public:
    int minCut(string s) {
        int n = s.size();  
        //dp[i][j]:[i, j]区间是否为回文串
        vector<vector<bool>> isPal(n, vector<bool>(n, false));
        
        for (int i = n - 1; i >= 0; i--)
            for (int j = i; j < n; j++)
                if (s[j] == s[i])
                    isPal[i][j] = i + 1 < j ? isPal[i + 1][j - 1] : true;
            
        vector<int> dp(n, INT_MAX);
        //dp[i]:到这个位置保存回文需要切多少刀
        for(int i = 0; i < n; i++)
        {
            if(isPal[0][i])
                dp[i] = 0;
            else
                //j == 0,表示区间就是[0, i],进入这里[0, i]一定不是回文,可以不处理
                for(int j = 1; j <= i; j++)
                {
                    //[j, i]是回文
                    //这个位置要保证回文需要到j - 1位置保持回文并且在这个位置再切一刀
                    if(isPal[j][i])
                        dp[i] = min(dp[i], dp[j - 1] + 1);
                }          
        }
        return dp[n - 1];
    }
};

4.最长回文子序列(中等)

链接:最长回文子序列

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    有第一题的经验,要充分利用回文串的性质,我们需要二维表。
    我们定义状态表示dp[i][j]:[i, j]区间的中的最大回文子序列长度

  2. 状态转移方程
    我们对[i, j]区间分析:
    (1)如果s[i] == s[j],那么我们只需要知道区间[i + 1, j - 1]的最大回文子序列长度,在这个长度的基础上加2即可,dp[i][j] = dp[i + 1][j - 1] + 2。
    (把s[i]和s[j]接在前面和后面)

    (2)如果s[i] != s[j],只有三种可能:
    ①s[i]可以接在[i + 1, j - 1]最长回文序列前面,dp[i][j] = dp[i][j - 1]。
    ②s[j]可以接在[i + 1, j - 1]最长回文序列后面,dp[i][j] = dp[i + 1][j]。
    ③s[i]或s[j]即不能接在[i + 1][j - 1]最长回文子序列前面,也不能接在后面,dp[i][j] = dp[i + 1][j - 1]。
    其中③无论如何都是小于等于①②的情况的,填表时无需理会③

  3. 初始化
    (1)对于dp[i][i](一个字符做回文),需要初始化为1,这里就放在填表中处理了。
    (2)其它位置初始化0即可。

  4. 填表顺序
    在这里插入图片描述
    填表顺序为行从下到上,每一行从左到右

  5. 返回值
    依据状态表示,返回值为dp[0][n - 1]

  • 代码实现
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        //dp[i][j]表示[i,j]区间中的最大回文子序列
        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++)
            {
                //a(s[i])  [i + 1, j - 1]  a(s[j]),[i + 1, j - 1]区间加2
                //不是的话[i + 1, j],[i, j - 1]取最大  
                if(s[i] == s[j])
                {
                    //dp[i][j] = (i + 1 < j) ? dp[i + 1][j - 1] + 2 : 2;
                    //刚好区间长度为2的时候[i + 1, j - 1]是无效区间
                    //这里无效区间刚好被初始化为0
                    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][n - 1];
    }
};

5.让字符串成为回文串的最小插入次数(困难)

链接:让字符串成为回文串的最小插入次数

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    要充分利用回文串的性质,我们需要一个二维表。
    我们定义状态表示为dp[i][j]:[i,j]区间修改成回文所需要的最小次数

  2. 状态转移方程
    对[i, j]区间进行分析:
    (1)s[i] == s[j]。只需要让[i + 1][j - 1]修改成回文即可,我们需要[i + 1][j - 1]区间修改成回文所需要的最小次数,即dp[i][j] = dp[i + 1][j - 1]

    (2)s[i] != s[j],这个时候需要修改[i, j]为回文,有两种选择:
    ①在最前面插入一个s[j],这个时候只需要再修改[i][j - 1]区间为回文即可(比如bad,前面补充变成dbad,需要"ba"区间的最小修改次数)。即dp[i][j] = dp[i][j - 1] + 1
    ②在最后面插入一个s[i],这个时候只需要再修改[i + 1][j]区间为回文即可(比如bad,后面补充变成badb,需要"ad"区间的最小修改次数)。即dp[i][j] = dp[i + 1][j] + 1
    取①②选择中修改次数最小的一方即可

  3. 初始化
    全都初始化为0即可

  4. 填表顺序
    和4题一样,填表顺序行从下到上,每一行从左到右

  5. 返回值
    依据状态表示,返回值为dp[0][n - 1]

  • 代码实现
class Solution {
public:
    int minInsertions(string s) {
        int n = s.size();
        //dp[i][j]表示[i,j]区间修改成回文所需要的最小次数
        vector<vector<int>> dp(n, vector<int>(n));
        //最后一行可以不开,但那样初始化麻烦
        for(int i = n - 1; i >= 0; i--)
        {
            for(int j = i + 1; j < n; j++)
            {
                //比如mbam,只需要知道"ba"区间的最小修改次数即可
                if(s[i] == s[j])
                {
                    //对于[i, j]长度2的情况,dp[i+1][j-1]刚好初始化为0
                    dp[i][j] = dp[i + 1][j - 1];
                }
                //比如bad,一种是前面补充->dbad,需要"ba"区间的最小修改次数
                //一种是后面补充->badb,需要"ad"区间的最小修改次数
                else
                {
                    dp[i][j] = min(dp[i][j - 1], dp[i + 1][j]) + 1;
                }
            }
        }
        return dp[0][n - 1];
    }
};

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

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

相关文章

httpd-tools的压力测试

httpd-tools httpd-tools 是一个包含一些基本工具和实用程序的软件包&#xff0c;用于与 Apache HTTP Server 进行交互和管理。它提供了一些常用的命令行工具&#xff0c;可以帮助你配置、管理和监控 Apache 服务器。ApacheBench 工具&#xff0c;用于进行性能测试和负载压力测…

文献阅读:LIMA: Less Is More for Alignment

文献阅读&#xff1a;LIMA: Less Is More for Alignment 1. 内容简介2. 实验设计 1. 整体实验设计2. 数据准备3. 模型准备4. metrics设计 3. 实验结果 1. 基础实验2. 消解实验3. 多轮对话 4. 结论 & 思考 文献链接&#xff1a;https://arxiv.org/abs/2305.11206 1. 内容简…

面试算法13:二维子矩阵的数字之和

题目 输入一个二维矩阵&#xff0c;如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和&#xff1f;对于同一个二维矩阵&#xff0c;计算子矩阵的数字之和的函数可能由于输入不同的坐标而被反复调用多次。例如&#xff0c;输入图2.1中的二维矩阵&#xff0c;以及左上角坐标…

SAP 操作:怎么设定屏幕前台字段显示/编辑

文章目录 前言一、步骤设定方式 前言 SAP将字段放进群组&#xff0c;通过对群组进行控制。 一、步骤 后勤常规-物料主数据-字段选择 设定方式 点击后面绿色按钮2.

map和set模拟实现

本期我们来对map和set进行模拟实现&#xff0c;此处需要红黑树基础&#xff0c;没有看过红黑树的小伙伴建议先去看看红黑树&#xff0c;如果没了解过map和set的小伙伴也建议先去看一看&#xff0c;博客链接我都放在这里了 C红黑树_KLZUQ的博客-CSDN博客 C-map和set_KLZUQ的博客…

stl案例二——员工分组

案例描述 公司今天招聘了10个员工&#xff0c;10名员工进入公司之后&#xff0c;需要指派员工在那个部门工作 员工信息有:姓名 工资组成;部门分为:策划、美术、研发 随机给10名员工分配部门和工资 通过multimap进行信息的插入 key(部门编号)value(员工…

能跑通的mmdet3d版本

能跑通的mmdet3d版本 1.0版本 2.0版本

Java项目:SSM的网上书城系统

作者主页&#xff1a;Java毕设网 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 一、相关文档 1、关于雅博书城在线系统的基本要求 &#xff08;1&#xff09;功能要求&#xff1a;可以管理个人中心、用户管理、图书分类管理、图书信息管理、…

C++入门知识

Hello&#xff0c;今天我们分享一些关于C入门的知识&#xff0c;看完至少让你为后面的类和对象有一定的基础&#xff0c;所以在讲类和对象的时候&#xff0c;我们需要来了解一些关于C入门的知识。 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对…

PTE 做题方法 Summarise Written Text and Write Essay

目录 Summarise Written Text 如何辨别关键点 Summarize Written Text #2 - 连接关键点 确定主语 SWT常见错误 SWT时间安排 Write Essay #1 - 评分规则 & 文章规划 Write Essay #2 - 范文学习 Write Essay #3 - 训练方法 Essay时间安排 you should get into your…

公众号迁移多久可以完成?

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;长期以来&#xff0c;由于部分公众号在注册时&#xff0c;主体不准确的历史原因&#xff0c;或者公众号主体发生合并、分立或业务调整等现实状况&#xff0c;在公众号登记主体不能对应实际运营人的情况下&…

【每日一题】1993. 树上的操作

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;深度优先搜索 写在最后 Tag 【深度优先搜索】【树】【设计数据结构】【2023-09-23】 题目来源 1993. 树上的操作 题目解读 本题是一个设计类的题目&#xff0c;对于设计类的题目就一步步的实现题目要求的成员方法即可…

Red Hat 8 重置root管理员密码

Linux系统&#xff1a;Red Hat Enterprise Linux release 8.8 (Ootpa) 确定你的Linux系统是否为RHEL 8&#xff08;Red Hat 8&#xff09;系统&#xff0c;在RHEL 8中&#xff0c;选择“活动”–>“终端”命令&#xff0c;然后在打开的终端中输入如下命令&#xff1a; [ro…

2023华为杯数学建模D题-域碳排放量以及经济、人口、能源消费量的现状分析(如何建立指标和指标体系1,碳排放影响因素详细建模过程)

可能建立的指标如下&#xff1a; 经济指标: 地区生产总值&#xff08;GDP&#xff09;人均GDP&#xff1b;第一产业&#xff08;农林部门&#xff09;产值&#xff1b;第二产业&#xff08;能源供应和工业部门&#xff09;产值&#xff1b;第三产业&#xff08;建筑和交通部门…

js中的类型转换

JavaScript 中有两种类型转换&#xff1a;隐式类型转换&#xff08;强制类型转换&#xff09;和显式类型转换。类型转换是将一个数据类型的值转换为另一个数据类型的值的过程。 隐式类型转换&#xff08;强制类型转换&#xff09;&#xff1a; 隐式类型转换是 JavaScript 自动…

【解决】Unity3D中无法在MQTT事件中执行Animator

问题原因&#xff1a; 解决方法&#xff1a; 解决过程 1、在 Unity 中创建一个名为 MainThreadDispatcher 的脚本&#xff0c;用于处理主线程操作。 using System.Collections.Generic; using UnityEngine;public class MainThreadDispatcher : MonoBehaviour {private stati…

基于springboot+vue的华山旅游网(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

【java】【SpringBoot】【四】原理篇 bean、starter、核心原理

目录 一、自动配置 1、bean加载方式&#xff08;复习&#xff09; 1.1 加载方式-xml方式生命bean 1.2 加载方式-xml注解方式声明bean 1.3 注解方式声明配置类 1.4 FactoryBean 1.5 proxyBeanMethod属性 1.6 使用Import注解导入 1.7 使用上下文对象在容器初始化完毕后注…

(第三百篇BLOG记录)写于博士毕业与入职之初-20230924

启 由于若干原因&#xff08;包括但不限于紧锣密鼓的完成博士毕业的一系列实验和论文撰写、学习各种百花齐放的有意思的领域、完成人生身份的重大转变&#xff09;&#xff0c;导致卡在299篇博客已经很久了&#xff0c;不过算了一下还是在一个较长时间维度上可以基本保持每周一…

CompletableFuture-FutureTask

2. CompletableFuture 语雀 2.1 Future接口理论知识复习 Future接口&#xff08;FutureTask实现类&#xff09;定义了操作异步任务执行一些方法&#xff0c;如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。 举例&#xff1a;…