给你一个字符串
s
,请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
视频讲解https://www.bilibili.com/video/BV17G4y1y7z9/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解https://programmercarl.com/0647.%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.html
⭐如果s[i + 1, j - 1]是回文子串 且 s[i] == s[j] 👉s[i, j]是回文子串
- 思路:
- 思路一,动态规划:
- dp数组含义:bool类型 dp[i][j] 表示 子串[i, j]是否回文
- 递推公式:只用维护 i <= j 的 dp[i][j],考虑以下情况
- 情况一:i == j,例如"a",true
- 情况二:i + 1 == j,例如"aa",true
- 情况三:i + 1 < j,看dp[i + 1][j - 1](此时dp[i + 1][j - 1]才有意义)
- 初始化:全false
- 遍历顺序:从下到上/从左到右
- 思路二,双指针:
- 遍历s,取中心点用于向两边对称位置扩展
- 以 i 为中心(奇数)
- 以 i 和 i + 1 为中心(偶数)
- 从中心向两边比较,统计回文子串的数目
- 遍历s,取中心点用于向两边对称位置扩展
- 思路一,动态规划:
- 代码:
// 思路一,动态规划:
class Solution {
public:
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;
}
};
// 简洁版:
class Solution {
public:
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] && (j - i <= 1 || dp[i + 1][j - 1])) {
result++;
dp[i][j] = true;
}
}
}
return result;
}
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(n^2)
// 思路二,双指针:
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;
}
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
视频讲解https://www.bilibili.com/video/BV1d8411K7W6/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解https://programmercarl.com/0516.%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.html
- 思路:
- dp数组含义:dp[i][j] 表示 子串[i, 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]);
- 初始化:dp[i][i] = 1;其余全0
- 遍历顺序:从下到上,从左到右
- 最终结果:dp[0][s.size() - 1];
- 代码:
class Solution {
public:
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];
}
};
⭐拓展:LeetCode 5.最长回文LeetCode 5.最长回文子串LeetCode 5.最长回文
- dp数组含义:dp[i][j] 表示 子串[i, j]的最长回文子串的长度
- 递推公式:
- s[i] 与 s[j] 相同 且 [i + 1, j - 1]是回文子串👉dp[i][j] = dp[i + 1][j - 1] + 2;
- 即 dp[i + 1][j - 1] == j - (i + 1)
- 记录最长回文子串[start, end]
- 每遇到一个回文子串[i, j]比较长度:j + 1 - i vs end + 1 - start
- 若 >,则更新start、end
- else👉dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
- s[i] 与 s[j] 相同 且 [i + 1, j - 1]是回文子串👉dp[i][j] = dp[i + 1][j - 1] + 2;
- 初始化:dp[i][i] = 1;其余全0
- 遍历顺序:从下到上,从左到右
- 最终结果:dp[0][s.size() - 1]👉字符串s的最长回文子串的长度
- 题目要求返回最长回文子串
class Solution {
public:
string longestPalindrome(string s) {
// 记录最长回文子串
int pos = 0; // 起始位置
int len = 0; // 长度
// dp[i][j]:子串[i, j]是否回文
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
// 从下到上,从左到右
for (int i = s.size() - 1; i >= 0; --i) {
for (int j = i; j < s.size(); ++j) {
if (s[i] == s[j] && (j <= i + 1 || dp[i + 1][j - 1])) {
dp[i][j] = true;
// 更新最长回文子串
if (j - i + 1 > len) {
pos = i;
len = j - i + 1;
}
}
}
}
return s.substr(pos, len);
}
};
动规五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划基础:
理论基础、509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯:Day 38
62.不同路径Ⅰ、63.不同路径Ⅱ:Day 39
343.整数拆分、96.不同的二叉搜索树:Day 41
背包问题系列
01背包理论基础二维、一维、416:Day 42
1049、494、474:Day 43
完全背包理论基础、518、377:Day 44
70(进阶)、322、279:Day 45
139、多重背包、背包问题总结:Day 50
打家劫舍系列:Day 51
打家劫舍Ⅰ:线
打家劫舍Ⅱ:环
打家劫舍Ⅲ:树形dp
子序列系列:
300、674、718:Day 56
1143、1035、53:Day 57
392、115:Day 58
583、72:Day 59
647、516:Day 60