647.回文子串
题目描述:
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
示例 1:
输入:s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
示例 2:
输入:s = "aaa" 输出:6 解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
代码:
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(); i >= 0; i --) {
for (int j = i; j < s.size(); j ++) {
if (s[i] == s[j]) {
if (j - i <= 1) {
dp[i][j] = true;
result ++;
} else if (dp[i + 1][j - 1] == true) {
dp[i][j] = true;
result ++;
}
}
}
}
return result;
}
};
代码解析
定义与初始化:
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false)); int result = 0;dp[i][j]表示子串s[i...j]是否为回文子串。- 初始值设为
false,表示所有子串尚未判断。 result用于计数回文子串的数量。
遍历顺序:
for (int i = s.size(); i >= 0; i --) { for (int j = i; j < s.size(); j ++) {- 外层循环从字符串末尾向前遍历
i。 - 内层循环从
i向字符串末尾遍历j。 - 这种遍历顺序保证在计算
dp[i][j]时,dp[i+1][j-1]已经被计算过。
- 外层循环从字符串末尾向前遍历
判断是否为回文子串:
if (s[i] == s[j]) { if (j - i <= 1) { dp[i][j] = true; result ++; } else if (dp[i + 1][j - 1] == true) { dp[i][j] = true; result ++; } }- 如果
s[i] == s[j],那么有两种情况:- 如果子串长度为 1 或 2(
j - i <= 1),直接为回文。 - 如果子串长度大于 2,检查内部子串
s[i+1...j-1]是否是回文(即dp[i+1][j-1]是否为true)。
- 如果子串长度为 1 或 2(
- 每次确认
dp[i][j] = true后,将result加 1。
- 如果
返回结果:
return result;- 返回统计的回文子串总数。
示例分析
输入: "abc"
过程:
- 初始化:
dp全为false,result = 0。
- 遍历:
i = 2, j = 2→s[2] == s[2]→dp[2][2] = true→result = 1i = 1, j = 1→s[1] == s[1]→dp[1][1] = true→result = 2i = 0, j = 0→s[0] == s[0]→dp[0][0] = true→result = 3
- 返回
result = 3。
输入: "aaa"
过程:
- 初始化:
dp全为false,result = 0。
- 遍历:
i = 2, j = 2→s[2] == s[2]→dp[2][2] = true→result = 1i = 1, j = 2→s[1] == s[2]→dp[1][2] = true→result = 2i = 1, j = 1→s[1] == s[1]→dp[1][1] = true→result = 3i = 0, j = 2→s[0] == s[2]→dp[0][2] = true→result = 4i = 0, j = 1→s[0] == s[1]→dp[0][1] = true→result = 5i = 0, j = 0→s[0] == s[0]→dp[0][0] = true→result = 6
- 返回
result = 6。
516.最长回文子序列
题目描述:
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。
代码:
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];
}
};
代码解析
定义与初始化:
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0)); for (int i = 0; i < s.size(); i ++) { dp[i][i] = 1; }- 定义一个二维数组
dp,dp[i][j]表示字符串s[i...j]的最长回文子序列长度。 - 初始化
dp[i][i] = 1,因为单个字符本身是长度为 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]); } } }- 遍历顺序:
- 外层从字符串末尾开始,
i逐渐向前移动。 - 内层从
i+1开始,j逐渐向后移动。 - 这种顺序确保每次计算
dp[i][j]时,dp[i+1][j-1]、dp[i+1][j]、dp[i][j-1]均已被计算。
- 外层从字符串末尾开始,
- 转移逻辑:
- 如果
s[i] == s[j],说明s[i]和s[j]能够扩展回文子序列,因此长度加 2,即: dp[i][j]=dp[i+1][j−1]+2dp[i][j] = dp[i+1][j-1] + 2 - 如果
s[i] != s[j],则取决于s[i+1...j]和s[i...j-1]的最长回文子序列,取两者的较大值: dp[i][j]=max(dp[i+1][j],dp[i][j−1])dp[i][j] = \max(dp[i+1][j], dp[i][j-1])
- 如果
- 遍历顺序:
返回结果:
return dp[0][s.size() - 1];- 返回整个字符串(
s[0...s.size()-1])的最长回文子序列长度。
- 返回整个字符串(
示例分析
示例 1:
输入: "bbbab"
过程:
- 初始化:
dp = [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]] - 逐步填表:
- i=4,j=4i=4, j=4:已初始化。
- i=3,j=4i=3, j=4:
s[3] != s[4]→dp[3][4] = max(dp[4][4], dp[3][3]) = 1 - i=2,j=3i=2, j=3:
s[2] != s[3]→dp[2][3] = max(dp[3][3], dp[2][2]) = 1 - i=2,j=4i=2, j=4:
s[2] != s[4]→dp[2][4] = max(dp[3][4], dp[2][3]) = 1 - i=1,j=2i=1, j=2:
s[1] == s[2]→dp[1][2] = dp[2][1] + 2 = 3 - 继续直到 i=0,j=4i=0, j=4。
- 最终
dp[0][4] = 4。 输出:4。