LeetCode回文串系列
一、动态规划
1.模板:最长回文子串
1)题目
给你一个字符串 s,找到 s 中最长的回文子串。
2)思路
- 动归:处理出i到j是否是回文串的数组,更新记录最长的回文子串长度。时间复杂度恒为n方。
- 中心扩展:以每个字符为中心进行扩展回文字符串,时间复杂度最坏为n方,最好为n。
3)做法
- 动归做法
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
int res = 0;
int start = 0;
bool dp[n + 1][n + 1];//表示以i为起点,j为终点的字符串是否是回文串
//从后往前遍历j,用到dp[i+1][j-1]需要先更新i更大的状态
for (int i = n - 1; i >= 0; i--) {
for (int j = i ; j < n; j++) {
if (j - i <= 1) dp[i][j] = s[i] == s[j];
else dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j]);
// 记录答案
if(dp[i][j] && j - i + 1 > res) {
res = max(res, j - i + 1);
start = i;
}
}
}
return s.substr(start, res);
}
};
- 中心扩展做法
class Solution {
public:
pair<int, int> expand(const string& s, int left, int right) {
//对象传引用,大大提高空间效率
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left--;
right++;
}
return {left + 1, right - 1};
}
string longestPalindrome(string s) {
int start = 0, end = 0;
for (int i = 0; i < s.size(); i++) {
auto [left1, right1] = expand(s, i, i);
auto [left2, right2] = expand(s, i, i + 1);
if (right1 - left1 > end - start) {
start = left1;
end = right1;
}
if (right2 - left2 > end - start) {
start = left2;
end = right2;
}
}
return s.substr(start, end - start + 1);
}
};
2.分割回文串
1)题目
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
2)思路
先处理出i到j是否是回文串的数组,然后用dfs去搜索所有答案
3)做法
class Solution {
public:
bool dp[20][20];
int n;
vector<vector<string>> res;
vector<string> ans;
void dfs(string s, int i) {
int j;
for (j = i; j < n; j++) {
if (dp[i][j]) {
ans.push_back(s.substr(i, j - i + 1));
if (j == n - 1) {
res.push_back(ans);
}
else dfs(s, j + 1);
ans.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
n = s.size();
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (j - i <= 1) dp[i][j] = s[i] == s[j];
else dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j]);
}
}
dfs(s, 0);
return res;
}
};
3.分割回文串II
1)题目
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
2)思路
先处理出i到j是否是回文串的数组,然后用f[i]记录以i结尾的字符串最少分割次数。
f[i] = min(f[j]) + 1, 0<=j<=I。
3)做法
class Solution {
public:
int dp[2010][2010];
int n;
int minCut(string s) {
n = s.size();
int res = 0;
//处理回文串数组
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (j - i <= 1) dp[i][j] = s[i] == s[j];
else dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j]);
}
}
int f[2010];//表示以i结尾的字符串最少分割次数
for (int i = 1; i < n; i++) f[i] = INT_MAX;
f[0] = 0;
for (int i = 1; i < n; i++) {
for (int j = 0; j <= i; j++) { //遍历以s[i]结尾的回文子串的起点
if(dp[j][i]) {
if(j) {
if(f[j - 1] < INT_MAX) f[i] = min(f[i], f[j - 1] + 1);
}
else f[i] = 0;
}
}
}
return f[n - 1];
}
};
4.回文子串个数
1)题目
Given a string s, return the number of palindromic substrings in it.
2)思路
- 动归:处理出i到j是否是回文串的数组,遍历第二遍记录回文串的个数。
- 中心扩展:以每个字符为中心进行扩展回文字符串,每个字符扩展完成统计长度。
3)做法
class Solution {
public:
pair<int, int> expand(const string& s, int left, int right) {
//对象传引用,大大提高空间效率
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left--;
right++;
}
return {left + 1, right - 1};
}
int countSubstrings(string s) {
int res = 0;
for (int i = 0; i < s.size(); i++) {
auto [left1, right1] = expand(s, i, i);
auto [left2, right2] = expand(s, i, i + 1);
res += (right1 - left1) / 2 + 1 + (right2 - left2 + 1) / 2;
}
return res;
}
};
二、valid Palindrome
1.125. Valid Palindrome
1)题目
Given a string s, return true if it is a palindrome, or false otherwise.
2)思路
主要处理非字符符号和大小写
3)做法
class Solution {
public:
bool isPalindrome(string s) {
string res;
for (auto c: s) {
if (isalpha(c)) res += toupper(c);
else if (isdigit(c)) res += c;
}
for (int i = 0; i < res.size() / 2; i++) {
if (res[i] != res[res.size() - i - 1]) return false;
}
return true;
}
};
2.680. Valid Palindrome II
1)题目
Given a string s, return true if the s can be palindrome after deleting at most one character from it.
2)思路
每个不符合条件的点位,尝试两种删除方法
3)做法
class Solution {
public:
bool validPalindrome(string s) {
for (int i = 0; i < s.size() / 2; i++) {
int j = s.size() - i - 1;
if (s[i] != s[j]) {
if (originalValid(s, i + 1, j) || originalValid(s, i, j - 1)) return true;
else return false;
}
}
return true;
}
bool originalValid(string &s, int i, int j) {
for (; i < j; i++, j--) {
if (s[i] != s[j]) return false;
}
return true;
}
};
3.1216. Valid Palindrome III
1)题目
Given a string s and an integer k, return true if s is a k-palindrome.
A string is k-palindrome if it can be transformed into a palindrome by removing at most k characters from it.
2)思路
dp[i][j]代表从i到j最少删除几个字符就能变成回文串,最后判断dp[0][n-1]是否小于k
3)做法
O(n^2) space
class Solution {
public:
bool isValidPalindrome(string s, int k) {
int n = s.size();
int f[n][n];
memset(f, 0, sizeof f);
for (int i = n - 2; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (s[i] == s[j]) f[i][j] = f[i + 1][j - 1];
else f[i][j] = min(f[i][j - 1], f[i + 1][j]) + 1;
}
}
return f[0][n - 1] <= k;
}
};
O(n) space
class Solution {
public:
bool isValidPalindrome(string s, int k) {
int n = s.size();
int f[n];
memset(f, 0, sizeof f);
int temp, prev;
for (int i = n - 2; i >= 0; i--) {
prev = 0; // f[i + 1][j - 1]
for (int j = i + 1; j < n; j++) {
temp = f[j]; // f[i + 1][j]
if (s[i] == s[j]) f[j] = prev;
else f[j] = min(f[j - 1], f[j]) + 1;
prev = temp;
}
}
return f[n - 1] <= k;
}
};