回溯:
可以归结为一个模板
result = [ ]
void backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return;
for 选择 in 选择列表:
做选择
backtrack(递归)
撤销选择
题目1:路径问题
class Solution {
public:
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
if(root == NULL){
return res;
}
find(root,expectNumber);
return res;
}
void find(TreeNode* root,int num){
if(root == NULL){ // 走到左/右的尽头 就要返回,去遍历另一个子结点
return;
}
arr.push_back(root->val);
if(num == root->val && root->left == NULL && root->right == NULL){
res.push_back(arr); //走到叶节点且刚好匹配,就把该路径放进结果集
}
else {
//全路径遍历,前序
find(root->left,num - root->val);
find(root->right,num - root->val);
}
arr.pop_back(); //回溯
}
private:
vector<int> arr;
vector<vector<int> > res;
};
题目2.子集问题
怎么想? 一共有1 2 3三个数,要求出所有子集,那么无非就是从头开始走,遇到一个元素就做两种操作:选 and 不选,所以每个元素有2种可能,时间复杂度为O(2^n)。比如对于1,①选1,把1放到temp数组,再把temp数组放到res结果集,然后递归后面的2,3。②不选1,然后递归后面的2,3。
所以我们只需要做这个事情:
从0开始,遇到一个数nums[i],就选这个数
temp.push_back(nums[i]);
res.push_back(temp);
dfs(i+1,…); //选完这个数,对后面剩余的数进行同样操作。
//既然上面选了这个数,为了得到所有可能,我就不选这个数,也就是把这个数弹出
temp.pop_back();
dfs(i+1,…); //不选这个数,然后对后面的数进行同样操作。
到此结束。
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
if(nums.empty()){
return res;
}
res.push_back(temp); //一开始要先放入空集合
fun(0,nums,res); //从0开始遍历
return res;
}
void fun(int i, vector<int>& nums,vector<vector<int> >& res){
if(i >= nums.size()){
return;
}
temp.push_back(nums[i]); //选这个数
res.push_back(temp);
fun(i+1,nums,res); //对剩余的数进行同样操作
temp.pop_back(); //不选这个数,回溯
fun(i+1,nums,res); //对剩余的数进行同样操作
}
vector<int> temp; //temp需要保持原状,所以不能定义为局部变量,要作为类成员
vector<vector<int> > res;
};
另一种,按照先前的回溯模板,写法不同,思路一样
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> temp;
dfs(res,temp,nums,0);
return res;
}
void dfs(vector<vector<int> > &res,vector<int> &temp,vector<int> nums,int begin){
if(i <= nums.size()){
res.push_back(temp);
}
for(int j = begin; j < nums.size(); ++j) { //扫描,注意从begin开始,因为不是全排列,不需要每一次都nums.size( )个数
temp.push_back(nums[j]); //做选择
dfs(res,temp,nums,j+1); //递归
temp.pop_back(); //回溯
}
}
};
题目3.子集升级版(去重问题)
思路:同上,只不过需要加上判断是否重复的操作。可以用一个set容器,把每一次的temp数组都装进去,因为set自动去重,可以通过判断set中是否有该数组来确定是否插入res中。
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<int> temp;
vector<vector<int>> res;
res.push_back(temp);
sort(nums.begin(),nums.end()); //先对这个数组排序,方便去重
dfs(res,temp,nums,0);
return res;
}
void dfs(vector<vector<int>> &res,vector<int> &temp,
vector<int> nums,int i)
{
if(i >= nums.size()){
return;
}
temp.push_back(nums[i]);
if(x.find(temp) == x.end()) {
res.push_back(temp);
x.insert(temp);
}
dfs(res,temp,nums,i+1);
temp.pop_back(); //回溯
dfs(res,temp,nums,i+1);
}
set<vector<int>> x;
};
题目4. 字符串全排列
vector<string> Permutation(string str) {
vector<string> res(0);
if(str.empty()){
return res;
}
fun(str,0,res);
sort(res.begin(),res.end()); //排序,确保从小到大
return res;
}
void fun(string str,int begin,vector<string> &res){
if(begin == str.size() - 1){
//为了不出现重复,只有容器中没有str才加进去(如aba这种情况)
if(find(res.begin(),res.end(),str) == res.end()){
res.push_back(str);
}
return;
}
for(int i = begin; i < str.size(); i++){ //循环
swap(str[i],str[begin]); //选择
fun(str,begin + 1,res); //对剩余的字符串做同样操作
swap(str[i],str[begin]); //回溯
}
}
void swap(char &a,char &b){
char temp = a;
a = b;
b = temp;
}
题目5. 数组全排列问题(无重复数字)
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
if (nums.size() <= 0) {
return res;
}
vector<int> visited(nums.size(), 0);
dfs(nums, 0, visited);
return res;
}
void dfs(vector<int> nums, int begin, vector<int> &visited) {
//全排列不是子集.每一个都必须包含所有的数,所以要temp.size() = nums.size()
if (temp.size() == nums.size()) {
res.push_back(temp);
}
//筛选,没用过的拿来用,防止重复
for (int i = 0; i < nums.size(); ++i) {
if (visited[i] == 0) {
visited[i] = 1; //表示已经用过了
temp.push_back(nums[i]);
dfs(nums, i + 1, visited);
visited[i] = 0; //回溯
temp.pop_back();
}
}
return;
}
private:
vector<vector<int>> res;
vector<int> temp;
};
题目6:括号生成问题
class Solution {
public:
vector<string> generateParenthesis(int n) {
if(n <= 0){
return res;
}
string temp("");
dfs(temp, n, n);
return res;
}
void dfs(string temp, int left, int right){ //两个参数表示的是 剩余的( 和 )
if(left == 0 && right == 0){
res.push_back(temp);
return;
}
//先装左括号,前提是还有左括号
if(left > 0){
dfs(temp + "(", left - 1,right); //左括号用了一个,个数-1
}
//再装右括号,前提是左括号比较多,也就是剩余的少
if(left < right){
dfs(temp + ")", left, right - 1);
}
}
private:
vector<string> res;
};