给你一个字符串 s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。回文串 是正着读和反着读都一样的字符串
示例 1:
cpp
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
cpp
输入:s = "a"
输出:[["a"]]
>>思路和分析:
- 切割问题,有不同的切割方式
- 判断回文
代码随想录的Carl 老师说:"切割问题类似组合问题 "!(这段文字来自代码随想录--分割回文串)
对于字符串abcdef:
- 组合问题 :选取 一个a 之后,在bcdef 中再去选取第二个 ,选取b 之后在cdef 中再选取第三个.....
- 切割问题 :切割 一个a 之后,在bcdef 中再去切割第二段 ,切割b 之后在cdef 中再切割第三段.....
>>回溯三部曲:
1).确定回溯函数参数
- path存放切割后回文的子串
- result 保存 path,作为结果集
- startIndex 来控制for循环的起始位置(表示下一轮 递归遍历的起始位置),还用来表示这条切割线.(切割过的地方,不能重复切割,和组合问题也是保持一致的【这句话来自代码随想录】)
cpp
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) // startIndex 就用来表示这条切割线
2).递归的终止条件
如果切割线切到了字符串最后面,表示找了一种切割方法,此时终止本层递归!也就是出现这种情况:startIndex >= s.size(),收集结果后直接return;
cpp
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
}
3).单层搜索的逻辑
>>问题**(O_O)?思考:在递归循环中如何截取子串**呢?
- [startIndex,i] :左闭右闭,表示子串的起始位置和终止位置,有截取区间就可以截取子串。substr(startIndex, i - startIndex + 1);
>>问题**(O_O)?思考:如何判断所截取子串是否为回文子串**呢?
- 判断是否为回文子串:isPalindrome(s, startIndex, i) ,用双指针法
- 如果是回文,就加入在vector<string> path 中,path用来记录切割过的回文子串
cpp
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 如果不是则直接跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
注意:切割过的位置 ,不能重复切割 ,应传入下一层 的起始位置为 i + 1,即
- backtracking(s, i + 1);
(1)判断是否为回文子串
cpp
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
cpp
bool isPalindrome(string s) {
int left = 0,right = s.size()-1;
while(left<=right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
(2)C++代码
cpp
class Solution {
private:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
public:
vector<vector<string>> partition(string s) {
backtracking(s, 0);
return result;
}
};
- 时间复杂度: O(n * 2^n)
- 空间复杂度: O(n^2)
cpp
class Solution {
public:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
bool isPalindrome(string s) {
int left = 0,right = s.size()-1;
while(left<=right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
void backtracking(string s,int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if(startIndex >= s.size()) {
result.push_back(path);
return;
}
for(int i=startIndex;i<s.size();i++) {
// 获取[startIndex,i]在s中的子串
string subStr = s.substr(startIndex,i-startIndex+1);
if(isPalindrome(subStr)) path.push_back(subStr);// 是回文子串
else continue;// 不是回文,跳过
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
vector<vector<string>> partition(string s) {
backtracking(s, 0);
return result;
}
};
参考和推荐文章、视频:
带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1c54y1e7k6/?p=67&spm_id_from=pageDriver代码随想录 (programmercarl.com)https://www.programmercarl.com/0131.%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.html#%E4%BC%98%E5%8C%96