动态规划算法汇总链接
题目链接
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
分析 dp 表,要判断 [i, j] 位置的子串是否为回文,首先要根据 s[i] 和 s[j] 的大小判定,具体如下:
s[i] != s[j], false
s[i] == s[j], i == j, true
i + 1 == j, true
j - i > 1, s[i+1][j-1] == true, true
s[i+1][j-1] == false, false
代码如下:
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n));
int ret = 0;
for(int i = n - 1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
// 默认都是 false,只需要处理 true 的位置
if(s[i] == s[j])
dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
if(dp[i][j])
ret++;
}
}
return ret;
}
};
题目链接
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
如上题分析,写 dp 方程。
在 dp[i][j] 且满足基本约束时,找到 len(即 j - i + 1)的最大值,
同时,由于 dp 表是从下往上(从后往前)填的,正好更新 begin。
代码如下:
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
int len = 1, begin = 0;
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] = i+1 < j ? dp[i+1][j-1] : true;
if(dp[i][j] && j-i+1 > len)
len = j - i + 1, begin = i;
}
}
return s.substr(begin, len);
}
};
题目链接
给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。
当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。
还是照上述方法,生成 dp 表,记录是否为回文子串,进行数据预处理;
再将字符分成三部分,依次遍历,如果 相应位置的 dp 值为 true,就可以直接返回啦。
代码如下:
class Solution {
public:
bool checkPartitioning(string s) {
int n = s.size();
// 1. 预处理:子串是否是回文
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] = i+1 < j ? dp[i+1][j-1] : true;
// 2. 字符串分成三段,枚举就好了
// [0, i) [i, j) [j, n)
for(int i = 1; i < n - 1; i++) // i 是第二段的起始
for(int j = i + 1; j < n; j++) // j 是第三段的起始
if(dp[0][i-1] && dp[i][j-1] && dp[j][n-1])
return true;
return false;
}
};
题目链接
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
同样先预处理数据,方便判断子串是否是回文串;
剩下的分析方法与 1.4 单词拆分题 一样:
首先是离 i 最近的 [j, i],找到能满足是回文的 j,
再找 [0, j-1] 的最小分割次数,正是和状态表示一样,于是有
dp[i], [0, i] 是回文,0
[0, i] 不是回文,有 0 < j <= i,[j, i] 是回文,求 min(dp[j]+1)
[j, i] 不是回文,不考虑
代码如下:
class Solution {
public:
int minCut(string s) {
int n = s.size();
// 1. 预处理:子串是否是回文
vector<vector<bool>> sub(n, vector<bool>(n));
for(int i = n - 1; i >= 0; i--)
for(int j = i; j < n; j++)
if(s[i] == s[j])
sub[i][j] = i+1 < j ? sub[i+1][j-1] : true;
// 2. 分割,是另一个dp问题咯~
vector<int> dp(n, 0x3f3f3f3f);
for(int i = 0; i < n; i++) // i 是第二部分(整体)的结尾
{
if(sub[0][i])
dp[i] = 0;
else
for(int j = 1; j <= i; j++) // j 是第二部分的开头(第一部分结尾的下一个)
if(sub[j][i])
dp[i] = min(dp[j - 1] + 1, dp[i]);
}
return dp[n-1];
}
};
题目链接
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
分析 dp 表,要判断 [i, j] 内的最长回文子序列长度,里面的最长回文子序列为 dp[i+1][j-1] ,所以只需要判断边界 s[i] 和 s[j] 的值是否相等,以这个条件作为情况判定:
s[i] == s[j], dp[i][j] = dp[i+1][j-1]+2
s[i] != s[j], dp[i][j] = max(dp[i+1][j], dp[i][j-1])
代码如下:
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n)); // dp[i][j] 表示 s 中 [i, j] 范围内满足回文的子序列的最长长度
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;
else
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}
};
题目链接
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
从最小状态入手,当 i == j 或者 一些特殊情况的时候,子串满足回文串条件插入次数为 0
s[i] == s[j], dp[i][j] = dp[i+1][j-1];
s[i] != s[j], if i+1 == j, dp[i][j] = 1; // 这一条可以被下面的语句覆盖,所以不需要写出来
if i+1 > j, dp[i][j] = min(dp[i+1][j], dp[i][j-1])+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+1; j < n; j++)
if(s[i] != s[j]) dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1;
else dp[i][j] = dp[i+1][j-1];
return dp[0][n-1];
}
};
如果本文对你有些帮助,欢迎 点赞 收藏 关注,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 若有差错恳请留言指正~~