第47天,动态规划part13,回文子串问题,编程语言:C++
目录
647.回文子串
文档讲解:代码随想录回文子串
视频讲解:手撕回文子串
学习:本题最简单的方法是采用回溯或者暴力解法,时间复杂度为O(n^3),通过两个循环确定起始点,一个遍历判断是否回文。
本题还可以采用动态规划的方法进行求解。首先进行分析,假如一个子串是回文串,那么如果此时加入的头尾元素相等,那此时新的字符串也是一个回文串。换句话说,当前字符串是否为回文串也取决于其内部是否为回文串和两端的数是否相等。从动归五部曲出发。
1.确定dp数组,以及下标的含义:我们需要判断当前字符串是否为回文串,因此我们可以设置一个二维dp数组,dp[i][j]表示下标范围[i, j]是否为回文串,是为true,不是为false。
2.确定递推公式,我们在遍历的过程中,需要比较s[i]和s[j],如果s[i] != s[j]显然就不是回文串dp[i][j] = false。如果s[i] == s[j]则有三种情况:①如果i = j, 也就是只有一个字符的时候,显然一个字符其本身符合回文串要求,因此dp[i][j] = true。②如果j - i = 1,也就是两个字符,显然两个相同字符,也符合回文串要求,因此dp[i][j] = true。③如果j - i > 1,也就是中间隔了超过一个字符,则我们需要判断内字符串是否是回文串,dp[i][j] = dp[i + 1][j - 1]。
3.初始化dp数组,我们把全部都初始化为false即可,后续会进行遍历。
4.确定遍历顺序,根据递推公式,我们可知需要右下角的值,因此需要从下往上,从左往右遍历。这里需要注意j 是要大于 i的。
5.举例推导dp数组
代码:
//时间复杂度O(n^2)
//空间复杂度O(n^2)
class Solution {
public:
int countSubstrings(string s) {
//动态规划:非常巧妙,令人咋舌
//1.确定dp数组,以及下标的含义
//dp[i][j]表示下标[i-j]的字符串是否是回文串
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
//2.确定递推公式
//若s[i] == s[j],判断三种情况i = j, j - i = 1, j - i > 1;
//3.初始化dp数组: 显然全部初始化为false(后续会进行遍历)
//4.确定遍历顺序
int result = 0; //记录回文串数量
for(int i = s.size() - 1; i >= 0; i--) {
for(int j = i; j < s.size(); j++) { //注意j是大于等于i的
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;
}
}
}
}
return result;
}
};
516.最长回文子序列
文档讲解:代码随想录最长回文子序列
视频讲解:手撕最长回文子序列
题目:516. 最长回文子序列 - 力扣(LeetCode)
学习:本题要得到一个最长的回文子序列,根据上题我们知道了,判断一个字符串是否为回文串,取决于其内串是否为回文串和端口两边的值。
依据上题,本题稍作更改即可
1.确定dp数组以及下标的含义:我们可以设置一个二维dp数组,dp[i][j]表示下标[i - j]中能够组成回文串的最长长度。dp[i][j]的值取决于其内部的回文串大小。
2.确定递推公式:我们在遍历的过程中,需要判断s[i] 和 s[j]是否相等。
如果s[i] == s[j],那么说明s[i] 和 s[j]能够加入回文串中,dp[i][j] = dp[i + 1][j - 1] + 2;
如果s[i] != s[j], 那么说明s[i]和s[j]对增加回文串长度没有作用,那么我们可以分别判断加入s[i]和s[j]哪个能够得到最长的回文子序列。也即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
3.初始化dp数组,根据两个递推公式dp[i][j] = dp[i + 1][j - 1] + 2,我们其实是没有单独考虑i = j的情况的,因此我们需要初始化一下i = j的情况,且一个字符肯定是回文串,因此dp[i][j] = 1。其余的我们初始化为0即可。
4.确定遍历顺序,由于我们需要依靠下方、左方以及左下方的值,因此我们应该从下往上,从左到右进行遍历。
5.举例推导dp数组:
代码:
//时间复杂度O(n^2)
//空间复杂度O(n^2)
class Solution {
public:
int longestPalindromeSubseq(string s) {
//动态规划
//1.确定dp数组以及下标的含义
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
//2.确定递推公式
//s[i] == s[j]的两种情况
//3.初始化dp数组
for(int i = 0; i < s.size(); i++) {
dp[i][i] = 1;
}
//4.确定递推公式
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]);
}
}
}
return dp[0][s.size() - 1];
}
};
动态规划总结篇
文档讲解:代码随想录动态规划总结
动态规划总算是完结了!
牢记动归五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划能够解决的题型类型:
- 动态规划基础:
- 背包问题系列:
- 打家劫舍系列:
- 股票系列:
- 子序列系列:
每个题型都需要好好理解,多加练习,二刷再来!