一、回文子串
题目一:647. 回文子串
给你一个字符串 s
,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
定义一个二维数组
dp[i][j]
,其中i
和j
分别表示字符串的起始和结束索引(包含i
和j
),dp[i][j]
表示子串s[i...j]
是否为回文串。如果是回文串,dp[i][j]
为真(或1),否则为假(或0)。动态规划算法步骤
初始化:首先初始化二维数组
dp
,所有元素设置为0。基本情况:
- 对于所有单字符子串
s[i...i]
,设置dp[i][i] = 1
,因为单字符总是回文。- 对于所有双字符子串
s[i...i+1]
,如果s[i] == s[i+1]
,则设置dp[i][i+1] = 1
。状态转移:对于长度大于2的子串,
s[i...j]
是回文当且仅当s[i] == s[j]
且s[i+1...j-1]
也是回文。因此,如果dp[i+1][j-1] == 1
且s[i] == s[j]
,则设置dp[i][j] = 1
。计数:遍历
dp
数组,统计值为1的元素个数,即为回文子串的总数
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
int count = 0;
for (int i = 0; i < n; i++) {
dp[i][i] = true;
count++;
}
for (int i = 0; i < n - 1; i++) {
if (s[i] == s[i + 1]) {
dp[i][i + 1] = true;
count++;
}
}
for (int len = 3; len <= n; len++) {
for (int i = 0; i + len <= n; i++) {
int j = i + len - 1;
if (s[i] == s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true;
count++;
}
}
}
return count;
}
};
二、最长回文子序列
题目一:516. 最长回文子序列
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
如果知道字符串
s[i+1...j-1]
的最长回文子序列,那么可以根据s[i]
和s[j]
的关系来确定s[i...j]
的最长回文子序列。动态规划算法步骤
定义状态:定义一个二维数组
dp
,其中dp[i][j]
表示字符串s[i...j]
的最长回文子序列的长度。初始化:对于所有
i
,dp[i][i] = 1
,因为单个字符的最长回文子序列长度为1。状态转移方程:
- 如果
s[i] == s[j]
,那么dp[i][j] = dp[i+1][j-1] + 2
,因为两端的字符可以加到s[i+1...j-1]
的最长回文子序列的两端,形成一个更长的回文子序列。- 如果
s[i] != s[j]
,那么dp[i][j] = max(dp[i+1][j], dp[i][j-1])
,因为最长回文子序列要么在s[i+1...j]
中,要么在s[i...j-1]
中。计算顺序:从底向上计算,即先计算小的子问题,再利用小的子问题来解决大的子问题。这意味着需要按照
j-i
的增序来遍历所有的子串。返回结果:
dp[0][n-1]
即为整个字符串s
的最长回文子序列的长度,其中n
是字符串s
的长度。
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = n - 1; i >= 0; --i) {
dp[i][i] = 1;
for (int j = i + 1; j < n; ++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][n - 1];
}
};