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 = 1
i = 1, j = 1
→s[1] == s[1]
→dp[1][1] = true
→result = 2
i = 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 = 1
i = 1, j = 2
→s[1] == s[2]
→dp[1][2] = true
→result = 2
i = 1, j = 1
→s[1] == s[1]
→dp[1][1] = true
→result = 3
i = 0, j = 2
→s[0] == s[2]
→dp[0][2] = true
→result = 4
i = 0, j = 1
→s[0] == s[1]
→dp[0][1] = true
→result = 5
i = 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
。