回溯递归思想的一些经典算法题

回溯:
可以归结为一个模板

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;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值