动态规划终于要刷完了!虽然动规五部曲已经烂熟,也刷了有二三十题经典的动态规划题,但是自己实际做题时未必能用的很好,一方面是有些题目实在很难想到用动规解题,另一方面是即使想到动态规划,往往卡在状态的定义和状态转移上(更别说一些细节了,比如初始化),反复刷经典题目其实只是在学习方法论,关键还是得活学活用,毕竟做题时不会预先告诉你这时什么类型的题可以套什么思路~
LeetCode 647 回文子串
题目链接:647. 回文子串 - 力扣(Leetcode)
一开始看题会如讲解所说的陷入一个误区,就是用编辑距离的方式定义dp数组,dp[i]和dp[i-1]之间不知道怎么构造状态转移。回文的性质使得解题需要打开思路,如果一个字符串中首尾字符相同,去掉首尾字符后的子串如果是回文的,那么整个字符串就是回文的。这里对于单个字符串也要使用二维的dp数组,dp[i][j]定义为s[i : j](左闭右闭区间)是否为回文串,遍历i和j就枚举了子串的首尾位置,又因为j要大于等于i(等于时为单个字符,也是回文的),遍历时需要将j初始化为i。
定义好dp数组后,状态转移上,s[i]和s[j]不相等,s[i : j]必然不能构成回文子串;如果s[i]==s[j],dp[i][j]的结果取决于dp[i+1][j-1](除去s[i]和s[j]之后的子串是否为回文子串),从而也决定了遍历顺序上,i 需要倒序遍历,j 则可以正序遍历。
class Solution {
public:
int countSubstrings(string s) {
int n = s.length();
vector<vector<bool>> dp(n, vector<bool>(n, false));
int result = 0;
for(int i = n - 1; i >= 0; i--){
for(int j = i; j < n; j++){
if(s[i] == s[j]){
if(j - i <= 1){
dp[i][j] = true;
result++;
}
else if(dp[i+1][j-1]){
// 遍历顺序决定了i+1不会越界
dp[i][j] = true;
result++;
}
}
// else dp[i][j] = false; 初始化已经为false,可以不写
}
}
return result;
}
};
动态规划做多了,也没想过用双指针。双指针的时间复杂度也是O(n^2),但省去了dp数组的空间复杂度,主要方法就是以某一个或两个字符为中心点向左右遍历,判断能否构成回文(自己懒得写,贴上代码随想录的C++实现):
class Solution {
public:
int countSubstrings(string s) {
int result = 0;
for (int i = 0; i < s.size(); i++) {
result += extend(s, i, i, s.size()); // 以i为中心
result += extend(s, i, i + 1, s.size()); // 以i和i+1为中心
}
return result;
}
int extend(const string& s, int i, int j, int n) {
int res = 0;
while (i >= 0 && j < n && s[i] == s[j]) {
i--;
j++;
res++;
}
return res;
}
};
LeetCode 516 最长回文子序列
题目链接:516. 最长回文子序列 - 力扣(Leetcode)
相比于上一题,子序列不要求连续,定义区间s[i: j]之间的最长回文子序列的长度为dp[i][j],如果s[i]==s[j],dp[i][j] = dp[i-1][j-1]+2;如果s[i] != s[j],也就是不能可以只在s[i-1: j-1]的基础上在左边加上s[i],或者在右边考虑加上s[j],看是否能让回文子序列的长度增加,二者取最大即可。
遍历顺序和回文子串中的类似,由于dp数组含义不同,这里dp数组还要做额外的初始化,将dp[i][i]均初始化为1,因为在遍历过程中无法遍历到单个字符的情况:文章来源:https://www.toymoban.com/news/detail-414440.html
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> dp(n, vector<int>(n));
for(int i = 0; i < n; i++) dp[i][i] = 1;
for(int i = n - 1; i >= 0; i--){
for(int j = i + 1; j < n; j++){
// j = i的情况已经初始化,j从i+1开始遍历
if(s[i] == s[j]){
dp[i][j] = dp[i + 1][j - 1] + 2;
}
else{
// 第一项:在右边加入s[j];第二项:在左边加入s[i];
dp[i][j] = max(dp[i + 1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1]; // s[0 : n - 1]之间的最长回文子序列的长度(左闭右闭区间)
}
};
这一题对于我来说不是那么好想,因为连续的限制更多,状态转移其实会更明确一些。文章来源地址https://www.toymoban.com/news/detail-414440.html
到了这里,关于算法刷题打卡049 | 动态规划17的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!