给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
原题链接
解题思路: 本题解题的方法是回溯剪枝解法,解题的形式这里给出两种,即截取子串组合[参考博客题139. 单词拆分]和路径伸缩(常规的回溯写法)第一种形式解法直接给出代码,思路见参考博客总结,思路类似。
class Solution {
public:
bool isPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left++] != s[right--]) return false;
}
return true;
}
vector<vector<string>> partition(string s, unordered_map<string, vector<vector<string>>> &m) {
if (s.empty()) return {};
if (m.count(s)) return m[s];
vector<vector<string>> res;
for (int i = 1; i <= s.size(); ++i) {
string sub = s.substr(0, i);
if (isPalindrome(sub)) {
if (sub == s) {
res.push_back({sub});
break;
}
vector<vector<string>> t = partition(s.substr(i));
for (auto &a : t) {
a.insert(a.begin(), sub);
res.push_back(a);
}
}
}
return m[s] = res;
}
vector<vector<string>> partition(string s) {
unordered_map<string, vector<vector<string>>> m;
return partition(s, m);
}
};
第二种形式解法是采取路径伸缩,在深度优先遍历过程中维护一个out数组,把它想象成一个可伸缩的遍历轨迹(路径),若能遍历到终点,则这个遍历轨迹上的序列即可作为结果集中一个元素。
class Solution {
public:
bool isPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left++] != s[right--]) return false;
}
return true;
}
void helper(string &s, int i, vector<string> out, vector<vector<string>>& res) {
if (i >= s.size()) res.push_back(out);
for (int j = i; j < s.size(); ++j) {
string sub = s.substr(i, j - i + 1);
if (isPalindrome(sub)) {
out.push_back(sub);
helper(s, j + 1, out, res);
out.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
vector<vector<string>> res;
helper(s, 0, {}, res);
return res;
}
};
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
原题链接
解题思路: 如果将题131的解法略微调整用到此题中,对应的解法复杂度太高,OJ会TLE,联想到「 求元素序列所有情况可以考虑用暴力搜索,求元素是否满足某种特征或者个数,即对应一个值,可以考虑用动态规划」,OK,此题依然可以这么考虑,定义dp[i]为字符串[0,i]使得每个子串为回文传所做的最少分割次数,那么对[0,i]内每个字符都做分割,分割的最少次数即为dp[i],每个dp[i]的初始值为i,因为对于长度为(i+1)的字符串,最多做i次分割即可使每个子串为回文串。在判断回文串时,再次使用动态规划,定义palindrome[i][j]
为[i,j]字符串是否为回文,状态移动方程为palindrome[i][j]=(s[i]==s[j]&&palindrome[i+1][j-1])
.
思路参考自博客.
class Solution {
public:
int minCut(string s) {
if (s.empty()) return 0;
int n = s.size();
vector<vector<bool>> palindrome(n, vector<bool>(n));
vector<int> dp(n);
for (int i = 0; i < n; ++i) {
dp[i] = i;
for (int j = 0; j <= i; ++j) {
if (s[i] == s[j] && (i - j < 2 || palindrome[j + 1][i - 1])) {
palindrome[j][i] = true;
dp[i] = (j == 0) ? 0 : min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[n - 1];
}
};
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。
题目链接
注:此题作为题132解法的补充,dp[i][j] = s[i] == s[j] &&dp[j + 1][i - 1])
,此状态转移方程要保证在更新dp[i][j]
值之前dp[j + 1][i - 1]
要被更新过,之前的做法是用长度来控制,依次更新长度为1,2,…,n的字符串,这样能保证在更新长度为len之前,长度为len-2的已经被更新。但是此题的做法也可以保证这点,可以这么分析,我们先遍历[0,i],这个过程会遍历i-1,同时在遍历[0,i-1]过程中,也会遍历j+1(如果在[0,i-1]范围内),因此在更新dp[j][i]
时,边界j+1和i-1已经被更新过.
class Solution {
public:
int countSubstrings(string s) {
int n = s.size(), res = 0;
vector<vector<bool>> dp(n, vector<bool>(n));
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
if (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1])) {
dp[j][i] = true;
++res;
}
}
}
return res;
}
};
——————————————
对于字符串的一般灵活度比较大,很难总共出一个不变的模板,比较难的题一般优先思考DP解法,DFS解法,还有Two pointer等等.