今日内容:647.回文子串,516.最长回文子序列,动态规划总结篇。
心得:长达11天的动态规划结束了,接下来是图论。下周开始背八股了。
647.回文子串
首先要对回文子串的特点进行分析,如果已知s[1]、s[2]、s[3]是回文子串,那么只需要判断s[0]和s[4]是否相等。
即判断一个子字符串(范围[i, j])是否回文,依赖于子字符串(范围[i + 1, j - 1])是否回文。
- 确定dp数组以及下标的含义:dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的⼦串是否是回⽂⼦串,如果是dp[i][j]为true,否则为false。
- 确定递推公式:如果s[i] != s[j],则dp[i][j] = false;如果s[i] = s[j],有三种情况:(1)i = j,代表同一个字符,例如a,一定是回文子串;(2)j - i = 1,例如aa,也是回文子串;(3)j - i > 1,例如abcba,只需要判断dp[i + 1][j - 1],即bcb是不是回文子串,如果是就返回true。
if(s[i] != s[j]) dp[i][j] = false;
if(s[i] == s[j]){
if(j - i <= 1) {
result++;
dp[i][j] = true;
}
else{
if(dp[i + 1][j - 1] == true){
result++;
dp[i][j] = true;
}
else dp[i][j] = false;
}
}
- dp数组如何初始化:根据递推公式可知dp[i][0]一定需要初始化,表示子字符串范围[i,0]是否是回文子串,很明显,当i = 0时,只有一个字符,一定是回文子串,其余都不是回文子串。则dp[0][0] = true,其余初始化为false。(但是因为j是从=i开始的,因此相当于dp[0][0] = true的初始化在循环中可以实现,所以直接全部设置为false就好了。)
- 确定遍历顺序:可以看到i是依赖i + 1,j依赖j - 1,那么遍历顺序是从下到上,从左到右的。
- 举例推导dp数组
int countSubstrings(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
int result = 0;
for(int i = s.size() - 1; i >= 0; i--){
for(int j = i; j < s.size(); j++){
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;
}
时间复杂度:O(n^2);
空间复杂度:O(n^2);
516.最长回文子序列
需要找出最长的回文子序列,并返回长度。回文子串是连续的,但是回文子序列不一定是连续的。
- 确定dp数组以及下标的含义:dp[i][j]:字符串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]和s[j]的同时加⼊并不能增加[i,j]区间回⽂⼦序列的⻓度,那么分别加⼊s[i]、s[j]看看,哪⼀个可以组成最⻓的回⽂⼦序列。dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
- dp数组如何初始化:递推公式是计算不到i和j相同时候的情况。所以手动初始化当i=j时,dp[i][j] = 1,其余初始化为0。
- 确定遍历顺序:根据递推公式同样是从下到上,从左向右的遍历顺序。
- 举例推导dp数组
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for(int i = 0; i < s.size(); i++) dp[i][i] = 1;
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];
}
时间复杂度:O(n^2);
空间复杂度:O(n^2)。
动态规划总结篇
模版如下
- 确定dp数组以及下标的含义:
- 确定递推公式:
- dp数组如何初始化:
- 确定遍历顺序:
- 举例推导dp数组
对于每道题,在草稿上写出dp数组的含义和递推公式就成功了一大半。