回溯
又到了我们的力扣代码时间,这次的题目又需要靠我们的老朋友来解决————回溯。
先来回忆一下,什么是回溯
回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。搜索到某个结点时,总是要先判断该结点是否肯定包含问题的解,如果不包含,则跳过以该结点为根的子树的系统搜索,逐层向祖先结点回溯;否则就进入该子树,继续按照深度优先的策略进行搜索。回溯法在求问题的所有解释要回溯到根,并且根节点的所有子树都要被搜索遍之后才会结束,而在寻求任一个解时,通常搜索到问题的一个解就可以结束。
总的来说,类似于一颗树的结构
模板
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
回溯算法的框架:
const result = []
function backtrack(路径, 选择列表) {
if 满足结束条件:
result.push(路径)
return
for 选择 of 选择列表:
做出选择
backtrack(路径, 选择列表)
撤销选择
}
回顾完以后,我们直接看题
例题
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
思路分析
我们先来审题,将s分割成子字符串后,我们要使每个子串都是回文串,并且单个字符都是回文串,那么每个字符串至少都有一种分割方法使其每个子串回文,也就是单个字母为单个子串,就可以做到。
我们就用示例s="aab"
来分析一下
首先,分割出a
来,判断是回文串后,接下来判断剩下的ab
,将ab
看做一个整体,如果ab
是回文串,那么我们就返回结果,因为下一步已经超过下标索引了;如果不是回文串,那么我们就返回上一步骤的ab
,将a
和b
分开,来分别判断a
b
是否为回文串;如果a
为回文串,那么就判断剩余的b
是否为回文串,否则,这条路就走不通了,因为已经不可再次分割了。当然很明显,第一次分割到最后的时候,一定是一个单个字母为子串的结果。
那么第二次分割则是将aa
看作一个整体,判断是否为回文串,如果不是,那么后续就不用判断了,因为该字符串已经不是回文串了,不符合条件,假如aa
不是回文的话,我是说假如,那么这个时候我们就直接判断aab
是否为回文串了。那么这里aa
是一个回文,只需要判断b
是否为回文串了,如果是就可以直接返回结果了,不是回文串,下一步也超过了下标索引,
后续步骤也是这样,我们来看看图解
图解
具体代码
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) {
result.clear();
path.clear();
backtracking(s, 0);
return result;
}
};
代码分析
我们利用我们的回溯模板来分析
const result = []
function backtrack(路径, 选择列表) {
if 满足结束条件:
result.push(路径)
return
for 选择 of 选择列表:
做出选择
backtrack(路径, 选择列表)
撤销选择
}
路径对应s,选择列表则是我们的下标索引
结束条件:即下标索引超过字符串最大长度的时候
做选择:如果是回文子串,就记录下来,否则,寻找下一个下标索引,对应我们的横向截取
backtrack 也就是深度遍历,对应我们的纵向截取
后面一步对应我们的回溯过程
isPalindrome就是判断是否回文,我们传入字符串以及首字符及最后一个字符的下标,利用双指针法来判断。start
和end
两边同时收缩,比较对应的字母是否相同,只要有一个不同,就返回false,否则,返回true。
总结
模板很值得我们去记住,对付一些回溯题,我们的模板还是有一定效果的,但是不一定所有的回溯都适合这个模板,可能某些地方还需要改动,我们还是需要随机应变的。
我是小白,我们一起学习算法,谢谢!