专题二十三_动态规划_回文串系列问题_算法专题详细总结

目录

动态规划

回文串系列问题

1. 回⽂⼦串(medium)

解析:

解决回文串问题,这里提供三个思路:

1.中心扩展法:n^2 / 1

2.马拉车算法:n / n

3.动态规划算法:n^2 / n^2

1.状态表达式:

2.状态转移方程:

3.初始化:

4.填表顺序:

5.返回值:

代码编写:

总结:

2. 最⻓回⽂⼦串(medium)

解析:

代码编写:

总结:

3. 回⽂串分割IV(hard)

解析:

代码编写:

总结:

4. 分割回⽂串II(hard)

代码编写:

总结:

5. 最⻓回⽂⼦序列(medium)

代码编写:

总结:

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

解析:

代码编写:

总结:

总结不易~本节对我有很大帮助和收获,希望对你也是~!!!


动态规划

经过前面几个专题的学习,特别是子数组系列、子序列问题等的动态规划,我相信到这里大家都有了质的飞跃,现在进入回文串系列问题,一定能有很多收获的~

回文串系列问题

1. 回⽂⼦串(medium)

题目意思还是很简单的,就是求出一个字符串内所有子串的回文串数目

解析:

解决回文串问题,这里提供三个思路:

1.中心扩展法:n^2 / 1
2.马拉车算法:n / n
3.动态规划算法:n^2 / n^2

这里的中心扩展法是最简单的一种算法,一般适用于简单的求解单个字符串是否是回文串的问题;其中马拉车算法是一种很复杂但是很优的算法,只能解决回文串这一种问题,而且学习成本很高,所以这里还是不推荐,如果你有强烈欲望,可以自己学习一下;再就是动态规划思想,是虽然这里的时间复杂度和空间复杂度都很高,但是我们学习的是这一种算法思想,通过动态规划的思想就能很轻松的将困难题变成简单题~

1.状态表达式:

由于一个字符串要有头尾两个字符才能判断当前的字符串是否是回文串:

所以要设置dp[i][j]表示:以i位置为开头,j位置为结尾的字符串是否是回文串

这样就可以将整个字符串的所有有两个字符位置表示的子串都在一个二维数组内表示出来;

2.状态转移方程:

只有在s[i] == s[j] 的时候才有讨论当前子串是否为回文串的意义,所以分三类讨论:

1.当i == j 时 两字符重叠,是回文串

2.当i+1 == j 时 s[i] == s[j] 并且两字符相邻,所以是回文串

3.当s[i] == s[j],但是两字符不相邻,所以就要缩小范围,来继续判断【i+1,j-1】这个范围内的字符串是否是回文串

3.初始化:

无需初始化,因为不会存在越界的问题,通过状态表达式,只需要判断当前位置dp[i][j]是否是满足回文串的问题

4.填表顺序:

由于dp内只存在这两种位置的访问情况,所以必须要先直到【i+1,j-1】才能知道【i,j】这个位置,所以填表顺序是从下往上,从左往右填表;即行数i倒着填,列数j正着填

5.返回值:

最后用ret统计dp内有多少个true即可

代码编写:

class Solution {
public:
    int countSubstrings(string s) {
        int n=s.size();
        vector<vector<int>> dp(n,vector<int>(n));
        int ret=0;

        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]==s[j])
                {

                    if(i==j||i+1==j) dp[i][j]=1;
                    else if(i+1<n&&j-1>=0) dp[i][j]=dp[i+1][j-1];
                }
                if(dp[i][j]) ret++;
            }
        }
        return ret;
    }
};

总结:

由上面给出的求解回文串的三种方法中,动态规划思想是一种极为重要的思路,通过这题就可以看出动态规划思路很清晰明了,可以很简单的就将hard转换为easy

2. 最⻓回⽂⼦串(medium)

返回最长的一个回文子字符串

解析:

这一题的思路简直跟上一题一模一样,只是返回的内容不同,我们同样是要遍历所有的二维数组,就是遍历所有子串的情况,然后来判断当前位置是否满足回文串的条件,最后返回最长的即可:

 1.状态表达式:

由于一个字符串要有头尾两个字符才能判断当前的字符串是否是回文串:

所以要设置dp[i][j]表示:以i位置为开头,j位置为结尾的字符串是否是回文串

这样就可以将整个字符串的所有有两个字符位置表示的子串都在一个二维数组内表示出来;

2.状态转移方程:

只有在s[i] == s[j] 的时候才有讨论当前子串是否为回文串的意义,所以分三类讨论:

1.当i == j 时 两字符重叠,是回文串

2.当i+1 == j 时 s[i] == s[j] 并且两字符相邻,所以是回文串

3.当s[i] == s[j],但是两字符不相邻,所以就要缩小范围,来继续判断【i+1,j-1】这个范围内的字符串是否是回文串

3.初始化:

无需初始化,因为不会存在越界的问题,通过状态表达式,只需要判断当前位置dp[i][j]是否是满足回文串的问题

4.填表顺序:

由于dp内只存在这两种位置的访问情况,所以必须要先直到【i+1,j-1】才能知道【i,j】这个位置,所以填表顺序是从下往上,从左往右填表;即行数i倒着填,列数j正着填

5.返回值:

只需要返回最长的回文串即可,那么就要ret来记录最长的长度,_ret来记录当前最长的子字符串即可,返回_ret

代码编写:

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size(),ret=0;
        vector<vector<int>> dp(n,vector<int>(n));
        string _ret;
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]==s[j])
                {
                    if(i==j||i+1==j) dp[i][j]=1;
                    else if(i+1<n&&j-1>=0) dp[i][j]=dp[i+1][j-1];
                    if(dp[i][j]&&j-i+1>ret)
                    {
                        ret=j-i+1;
                        _ret=s.substr(i,ret);
                    }
                }
            }
        }
        return _ret;
    }
};

总结:

回文串系列的题目还是非常好想的,只要弄明白一题,后面就是照搬原样即可;

3. 回⽂串分割IV(hard)

将一个字符串分割成三块,如果这三块都是回文串,就直接返回true;否则返回false

解析:

这一题我们先要有一个整体的思路,肯定就是先要判断所有情况下的子字符串是否满足回文串,即设计二维数组来遍历所有下标,然后再来一个双重for将整个字符串分为3块,直接判断是否都是回文串并进行返回即可;

  1.状态表达式:

由于一个字符串要有头尾两个字符才能判断当前的字符串是否是回文串:

所以要设置dp[i][j]表示:以i位置为开头,j位置为结尾的字符串是否是回文串

这样就可以将整个字符串的所有有两个字符位置表示的子串都在一个二维数组内表示出来;

2.状态转移方程:

只有在s[i] == s[j] 的时候才有讨论当前子串是否为回文串的意义,所以分三类讨论:

1.当i == j 时 两字符重叠,是回文串

2.当i+1 == j 时 s[i] == s[j] 并且两字符相邻,所以是回文串

3.当s[i] == s[j],但是两字符不相邻,所以就要缩小范围,来继续判断【i+1,j-1】这个范围内的字符串是否是回文串

3.初始化:

无需初始化,因为不会存在越界的问题,通过状态表达式,只需要判断当前位置dp[i][j]是否是满足回文串的问题

4.填表顺序:

由于dp内只存在这两种位置的访问情况,所以必须要先直到【i+1,j-1】才能知道【i,j】这个位置,所以填表顺序是从下往上,从左往右填表;即行数i倒着填,列数j正着填

5.返回值:

要返回当前位置进行分割的三块字符串是否满足全是回文串,所以分割后的下标是暴力求解:

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

代码编写:

class Solution {
public:
    bool checkPartitioning(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])
                {
                    // if(i==j||i+1==j) dp[i][j]=true;
                    // else if(i+1<n&&j-1>=0) dp[i][j]=dp[i+1][j-1];
                    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;
    }
};

总结:

这一道hard题用动态规划的思路真的变的很简单,依旧是求出所有子串是否能成为回文串,然后再判断字符串分三块的问题直接暴力求解ok,所以这种思路一定要掌握,多复习也就记住了~

4. 分割回⽂串II(hard)

将一个字符串用最少的分割次数,让所有子串都是回文串,返回最少分割次数

解析:

1.状态表达式:

dp[i]表示:在s[0,i]位置上形成回文串的最少分割次数

2.状态转移方程:

dp[i] = min(dp[j - 1] + 1, dp[i]);

3.初始化:

由于dp表内是要求最小分割次数,每次都要取min,所以dp表内都初始化为INT_MAX

4.填表顺序:

从左往右填

5.返回值:

返回dp[n-1]

代码编写:

class Solution {
public:
    int minCut(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] = i + 1 < j ? dp[i + 1][j - 1] : true;

        vector<int> _dp(n, INT_MAX / 2);
        for (int i = 0; i < n; i++) {
            if (dp[0][i])
                _dp[i] = 0;
            else {
                for (int j = 1; j <= i; j++) {
                    if (dp[j][i])
                        _dp[i] = min(_dp[j - 1] + 1, _dp[i]);
                }
            }
        }
        return _dp[n - 1];
    }
};

总结:

这一题主要就是要想清楚怎么定义状态表达式,要明白了状态表达式的定义,说明出以i位置为结尾的最大分割次数,从而引出[0,i]是否是一整个回文,是就是0次;不是,就在进行判断,[1,j](0<j<=i) 判断当dp[j][i]这个位置是否为回文,是才能继续判断_dp[j-1]前面的最少分割次数

5. 最⻓回⽂⼦序列(medium)

求最长回文子序列的长度

解析:

1.状态表达式:

dp[i]表示:以i位置为结尾的最长回文子序列的长度;

dp[i][j]表示:在s[i,j]区间内最长的回文子序列的长度

2.状态转移方程:

                if(s[i]==s[j])
                {
                    if(i==j) dp[i][j]=1;
                    else if(i+1==j) dp[i][j]=2;
                    else dp[i][j]=dp[i+1][j-1]+2;
                }
                else 
                    dp[i][j]=max(dp[i][j-1],dp[i+1][j]);

3.初始化:

不用初始化,因为求最长的长度,开始为0/1都可以

4.填表顺序:

由于dp内只存在这两种位置的访问情况,所以必须要先直到【i+1,j-1】才能知道【i,j】这个位置,所以填表顺序是从下往上,从左往右填表;即行数i倒着填,列数j正着填

5.返回值:

dp[0][n-1]

代码编写:

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

总结:

根据经验+题目要求来判断状态表达式,这题一看就是子序列,那么就可以直接上手定义dp[i][j],这样更加便捷,因为关于子序列的判断就是求在s[i,j]这个区间内的回文子序列的长度大小,然后进行分类讨论,当s[i]==s[j] 和 s[i]!=s[j]两种情况下如何求出最长回文子序列的长度

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

插入最少的次数,让该字符串成为回文串

解析:

1.状态表达式:

关于「单个字符串」问题中的「回⽂⼦序列」,或者「回⽂⼦串」,我们的状态表⽰研究的对象⼀
般都是选取原字符串中的⼀段区域 [i, j] 内部的情况。这⾥我们继续选取字符串中的⼀段区域来研究:
状态表⽰: dp[i][j] 表⽰字符串 [i, j] 区域成为回⽂⼦串的最少插⼊次数。

2.状态转移方程:

在分别判断当s[i] == s[j] 和 s[i]!=s[j]时两者的讨论方式:

由于在s[i]!=s[j]的时候,dp[i][j-1] 和 dp[i+1][j]这两个区间内都要进行一次插入,让两端的字符相等

                if(s[i]==s[j]) dp[i][j]=i+1<j?dp[i+1][j-1]:0;
                else dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1;
关于「回⽂⼦序列」和「回⽂⼦串」的分析⽅式,⼀般都是⽐较固定的,都是选择这段区域的「左
右端点」的字符情况来分析。因为如果⼀个序列是回⽂串的话,「去掉⾸尾两个元素之后依旧是回
⽂串」,「⾸尾加上两个相同的元素之后也依旧是回⽂串」。因为,根据「⾸尾元素」的不同,可
以分为下⾯两种情况:
i. 当⾸尾两个元素「相同」的时候,也就是 s[i] == s[j]
1. 那么 [i, j] 区间内成为回⽂⼦串的最少插⼊次数,取决于 [i + 1, j - 1] 区间
内成为回⽂⼦串的最少插⼊次数;
2. i == j i == j - 1 [i + 1, j - 1] 不构成合法区间),此时只有 1 ~ 2 个相同的字符, [i, j] 区间⼀定是回⽂⼦串,成为回⽂⼦串的最少插⼊次数是此时 0。dp[i][j] = i >= j - 1 ? 0 : dp[i + 1][j - 1] 
ii. 当⾸尾两个元素「不相同」的时候,也就是 s[i] != s[j]
1. 此时可以在区间最右边补上⼀个 s[i] ,需要的最少插⼊次数是 [i + 1, j] 成为回⽂⼦串的最少插⼊次数 + 本次插⼊,即 dp[i][j] = dp[i + 1][j] + 1
2. 此时可以在区间最左边补上⼀个 s[j] ,需要的最少插⼊次数是 [i, j + 1] 成为回⽂⼦串的最少插⼊次数 + 本次插⼊,即 dp[i][j] = dp[i][j + 1] + 1
综上所述,状态转移⽅程为:
s[i] == s[j] 时: dp[i][j] = i >= j - 1 ? 1 : dp[i + 1][j -

3.初始化:

不用初始化

4.填表顺序:

从下往上,从左往右填

5.返回值:

返回dp[0][n-1];

代码编写:

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]=i+1<j?dp[i+1][j-1]:0;
                else dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1;
            }
        }
        return dp[0][n-1];
    }
};

总结:

这一题其实仔细总结一下,跟以前做的题目简直一模一样,只要考虑求出状态表达式和转移方程,那简直不要太轻松

总结不易~本节对我有很大帮助和收获,希望对你也是~!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ES实用面试题

一、es是什么&#xff0c;为什么要用它&#xff1f; ES通常是Elasticsearch的简称&#xff0c;它是一个基于Lucene构建的开源搜索引擎。Elasticsearch以其分布式、高扩展性和实时数据分析能力而闻名&#xff0c;广泛用于全文搜索、日志分析、实时监控等多种场景。 基本特点&am…

实现在两台宿主机下的docker container 中实现多机器通讯

基于我的实验背景 上位机&#xff1a;ubuntu 20.04 (docker humble 22.04) 下位机&#xff1a;ubuntu 22.04&#xff08;docker noetic 20.04&#xff09; 目标&#xff1a;实现在上位机中的docker container 容器的22.04环境去成功远程访问 非同网段的下位机的20.04的contai…

FakeLocation Linux | Windows关于使用教程一些规范说明

前言:使用教程&#xff08;FakeLocation版本请使用1.2.xxx&#xff09;| (1.3.xxx 未测试) 环境模块&#xff0c;是指代FakeLocation开启以后会把环境弄的异常,环境模块可以保证环境安全Dia 作为软件需要在Lsp框架里面勾选激活使用&#xff0c;并且开启增强模式FakeLocation 请…

指针的奥秘:深入探索内存的秘密

前言 在计算机编程的广阔天地中&#xff0c;指针作为一种独特的数据类型&#xff0c;它不仅是C语言的核心&#xff0c;也是理解计算机内存管理的基石。指针的概念虽然强大&#xff0c;但对于初学者来说&#xff0c;它常常是学习过程中的一个难点。本文旨在揭开指针的神秘面纱&a…

Mairadb 最大连接数、当前连接数 查询

目录 查询数据库 最大连接数 查询当前连接总数 环境 Mariadb 10.11.6 跳转mysql数据库&#xff1a; 查询数据库 最大连接数 show variables like max_connections; 注意; 这个版本不能使用 &#xff1a; show variables like ‘%max_connections%’; 会报错 &#xff…

电影风格城市夜景旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 电影风格城市夜景旅拍通过 Lightroom 调色&#xff0c;将城市夜晚的景色打造出如同电影画面般的质感和氛围。以独特的色彩和光影处理&#xff0c;展现出城市夜景的魅力与神秘。 预设信息 调色风格&#xff1a;电影风格预设适合类型&#xff1a;人像&#xff0c;街拍…

代码管理之Gitlab

文章目录 Git基础概述场景本地修改未提交&#xff0c;拉取远程代码修改提交本地&#xff0c;远程已有新提交 GitIDEA引入Git拉取仓库代码最后位置 Git基础 概述 workspace 工作区&#xff1a;本地电脑上看到的目录&#xff1b; repository 本地仓库&#xff1a;就是工作区中隐…

【FPGA】Verilog:利用 4 个串行输入- 串行输出的 D 触发器实现 Shift_register

0x00 什么是寄存器 寄存器(Register)是顺序逻辑电路中使用的基本组成部分之一。寄存器用于在数字系统中存储和处理数据。寄存器通常由位(bit)构成,每个位可以存储一个0或1的值。通过寄存器,可以设计出计数器、加法器等各种数据处理电路。 0x01 寄存器的种类 基于 D 触发…

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

微信小程序+Vant-自定义选择器组件(单选带筛选

实现效果 筛选是filter&#xff0c;搜索框如有显隐需要&#xff0c;需自行添加配置显隐参数弹出层高度样式需要手动修改&#xff0c;需自行添加配置高度参数.json文件配置"component": true, 实现代码 组件代码 <van-popup show"{{ show }}" posit…

【Linux课程学习】:环境变量:HOME,su与su - 的区别,让程序在哪些用户下能运行的原理,环境变量具有全局性的原因?

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 HOME环境变量&#xff1a; PWD环境变量&#…

Java基础 设计模式——针对实习面试

目录 Java基础 设计模式单例模式工厂模式观察者模式策略模式装饰器模式其他设计模式 Java基础 设计模式 单例模式 单例模式&#xff08;Singleton Pattern&#xff09; 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。适用场景&…

QRCode.toDataURL() vue3 uniapp h5在 Android环境下二维码显示不出来

“qrcode”: “^1.5.4” 修改前&#xff08;在浏览器里面是可以加载的&#xff09;&#xff1a; 查资料好像是Android上加载的是canvas&#xff0c;不是加载的img。 修改后&#xff1a; 这里val其实打印出来是svg代码&#xff0c;所以用v-html就好了。

数据结构——排序算法第一幕(插入排序:直接插入排序、希尔排序 选择排序:直接选择排序,堆排序)超详细!!!!

文章目录 前言一、排序1.1 概念1.2 常见的排序算法 二、插入排序2.1 直接插入排序2.2 希尔排序希尔排序的时间复杂度 三、选择排序3.1 直接选择排序3.2 堆排序 总结 前言 时间很快&#xff0c;转眼间已经到数据结构的排序算法部分啦 今天我们来学习排序算法当中的 插入排序 和 …

第32周:猴痘病识别(Tensorflow实战第四周)

目录 前言 一、前期工作 1.1 设置GPU 1.2 导入数据 1.3 查看数据 二、数据预处理 2.1 加载数据 2.2 可视化数据 2.3 再次检查数据 2.4 配置数据集 2.4.1 基本概念介绍 2.4.2.代码完成 三、构建CNN网络 四、编译 五、训练模型 六、模型评估 6.1 Loss和Accuracy…

【创建型设计模式】工厂模式

【创建型设计模式】工厂模式 创建型设计模式第二期&#xff01;本期介绍简单工厂模式和工厂方法模式。 简单工厂模式 简单工厂模式&#xff08;又叫作静态工厂方法模式&#xff09;&#xff0c;其属于创建型设计模式&#xff0c;简单工厂模式不属于设计模式中的 23 种经典模…

【Linux】安装cuda

一、安装nvidia驱动 # 添加nvidia驱动ppa库 sudo add-apt-repository ppa:graphics-drivers/ppa sudo apt update# 查找推荐版本 sudo ubuntu-drivers devices# 安装推荐版本 sudo apt install nvidia-driver-560# 检验nvidia驱动是否安装 nvidia-smi 二、安装cudatoolkit&…

上天入地 灵途科技光电技术赋能空间感知

近来&#xff0c;人工智能技术频频亮相各大马拉松赛事&#xff0c;成为引人注目的科技亮点。 11月3日&#xff0c;杭州马拉松首次启用了机器狗作为配速员&#xff0c;以稳定的节奏为选手提供科学的跑步节奏。 11月11日&#xff0c;亦庄半程马拉松的终点处&#xff0c;人形机器…

Java三大特性:封装、继承、多态【详解】

封装 定义 隐藏对象的属性和实现细节&#xff0c;仅对外公开接口&#xff0c;控制在程序中属性的读取和修改的访问级别便是封装。 在开发中造一个类就是封装&#xff0c;有时也会说封装一个类。封装可以隐藏一些细节或者包含数据不能被随意修改。 比如这是一个敏感的数据&a…

40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)

【实战】并发安全的配置管理器&#xff08;功能扩展&#xff09; 一、扩展思考 分布式配置中心 实现配置的集中管理支持多节点配置同步实现配置的版本一致性 配置加密 敏感配置的加密存储配置的安全传输访问权限控制 配置格式支持 支持YAML、TOML等多种格式配置格式自动…