最近两日的每日一题题目相似,其实方法也可以用回溯。但是Leetcode-132中,如果用之前的回溯会发现超时,此时对于回文数的判定需要用动态规划的方法减少时间复杂度。
Leetcode-131
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
本题其实与Leetcode-46 全排列类似,可以用回溯的方法进行遍历,得到所有排列组合。本题与Leetcode-46不同的是,遍历的方法不同,这也是错了很多次的地方。全排列只需要每次遍历的数据个数为1,而本题遍历的字符串的长度可以从1到 n 不等,因此dfs的参数尤为关键。
整体遍历顺序较为清晰,需要从第0个字节开始,假设到第 i 个字节为回文串,则下一次的遍历从第 i + 1个字节开始;在回溯的过程中,则判断从第 0 个字节到第 i + 1个字节的字符串是否为回文串。由于我们需要所有分割的字符串均为回文串,也就是当某一段字符串不为回文串,那么接下来的所有字符串均不需要考虑。
在一开始,dfs的参数选择为字符串的起始位置begin与结束位置end,下一次遍历很明显将会是以end + 1开始进行遍历,这样会导致一个问题,那就是无法遍历begin到 end + 1 的字符串,除非我们在main函数中对以0开始的字符串进行for循环遍历(或许还有别的方法,但是我想不出来),但是在之前的题目中,每增加一个特判总是会出现意想不到的情况,因此我对dfs的参数重新进行了选择。
这次我选择dfs的参数只有字符串开始位置begin,这样的好处就是我可以遍历所有以begin开始的字符串,不会出现上述问题,接下来就顺理成章了,代码如下所示:
class Solution {
public:
int len;
vector<vector<string>> v1;
vector<string> v2;
vector<vector<string>> partition(string s) {
len = s.size();
dfs(0, s);
return v1;
}
void dfs(int begin, string s){
if(begin == len){
v1.push_back(v2);
return ;
}else{
for(int i = begin; i < len; i++){
if(ishuiwen(begin, i, s)){
v2.push_back(s.substr(begin, i - begin + 1));
dfs(i + 1, s);
v2.pop_back();
}
}
}
}
bool ishuiwen(int begin, int end, string s){
while(begin <= end){
if(s[begin] != s[end])
return false;
begin++;
end--;
}
return true;
}
};
算法的时间复杂度为O((n ^ 2 )* (2 ^ n)):很明显,每一个字符组成的字符串均为回文串,因此我们会遍历任意的顺序字符串,而这些字符串的个数为2 ^ (n - 1)。同时,每一种划分方法需要 O(n ^ 2)的时间求出答案。
空间复杂度为O(n^2)。
Leetcode-132
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
提示:
1 <= s.length <= 2000
s 仅由小写英文字母组成
本题明显为上面问题的变式,只不过按照提示,字符串的长度最大可达到2000,很明显,时间会超出。
我想了很长时间都没有相出特别好的解法,直到看到了题解,发现题解是优先判断出所有的回文串,其时间复杂度为O(n ^ 2)。
其原理很简单,假设第 i 到第 j 个字符组成的字符串是回文串,那么当第 i - 1个字符与第j + 1个字符相等时,第 i - 1 到第 j + 1个字符组成的字符串也是回文串。其实现代码如下:
vector<vector<int>> g(n, vector<int>(n, true));
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
g[i][j] = (s[i] == s[j]) && g[i + 1][j - 1];
}
}
在回文串全部判定之后,其题目就较为明显了,其解法与Leetcode-300 最长递增子序列类似。以dp[i]表示第 i 个字符结尾的字符串最少可由多少个回文串组成。假如第 a 个字符到第 b 个字符组成的字符串是回文串,那么dp[b]可以由dp[a - 1] + 1表示,这样我们可以判断所有以第 b 个字符结尾的回文串,从而获取其最小表示,因此问题的答案其实就是dp[n - 1],其中 n 是字符串的个数。
代码如下:
class Solution {
public:
int len;
int minn = INT_MAX;
vector<int> v2;
vector<vector<bool>> v1;
//判断回文数预处理
void judge(string s){
for(int i = len - 1; i >= 0; i--){
for(int j = i + 1; j < len; j++){
v1[i][j] = (s[i] == s[j]) && v1[i + 1][j - 1];
}
}
}
int minCut(string s) {
len = s.size();
v2.assign(len, INT_MAX);
v1.assign(len, vector<bool>(len, true));
judge(s);
for(int i = 0; i < len; i++){
if(v1[0][i] == true){
v2[i] = 0;
}else{
for(int j = 0; j < i; j++){
if(v1[j + 1][i])
v2[i] = min(v2[i], v2[j] + 1);
}
}
}
return v2[len - 1];
}
};