LeetCode 回溯法

LeetCode78.子集全排列


大意:给一个数组如[1,2,3],求出全部的排列情况:
{
[],
[1],
[1,2],
[1,2,3],
[1,3],
[2],
[2,3]
[3]
}

有两种回溯思路来做:
1.双递归:(转自力扣,笨猪爆破组)
单看每个元素,都有两种选择:选入子集中,或不选入子集中。
比如[1,2,3],先考察 1,选或不选,不管选没选 1,都再考察 2,选或不选,以此类推。
画出递归树如下(解的空间树):
在这里插入图片描述
一个子树是一个递归调用,考察当前枚举的数,选它,是一个分支,不选它,又是一个分支,对应两个递归的子调用,在子调用中,考察下一个枚举的数,同样是两种选择:选或不选。
我们用指针index描述一个节点的状态,即当前递归考察的数字 nums[index]。
在这里插入图片描述

当index == nums 数组长度时,此时考察完所有数字,把当前的子集加入解集,结束当前递归分支。
(其实就是在找叶子节点)

为什么要回溯?
因为不是找到一个子集就完事。
找到一个子集,结束了递归,要撤销当前的选择(从 list 中删掉),回到选择前的状态,做另一个选择:不选当前的数,基于不选,往下递归,继续生成子集。
回退到上一步,把路走全,才能在包含解的空间树中,回溯出所有的解。
在这里插入图片描述

class Solution {
public:
	vector<vector<int>> result;
	vector<int> path;
	void dfs(vector<int> &nums, int i)
	{
		if (i == nums.size()) //指针越界
		{
			result.push_back(path);//加入解集
			return;//返回上一层
		}
		path.push_back(nums[i]);//选择当前这个数
		dfs(nums, i + 1);//往下递归
		path.pop_back();//不选择当前这个数
		dfs(nums, i + 1);//往下递归
	}
	vector<vector<int>> subsets(vector<int>& nums) {
		dfs(nums, 0);
		return result;
	}
};

2.迭代+单递归
我们可以不设置递归的结束条件,让递归自然地结束。
在递归函数中,用 for 循环枚举出所有当前可选的选择,比如,选第一个数时,有 1 2 3 可选。
选了 1,接下去选第二个数,有 2 和 3 可选,选了 2,接下来只有 3 可选……如下图。
不同于第一种思路,我们不设置递归的出口,但每次递归调用,传入的指针都基于当前选择+1,可选的范围变小了,即 for 循环的遍历范围变小了,一直递归到「没有可选的数字」,for 循环就不会落入递归,自然结束掉,整个DFS就结束了。
并且,我们不是等到递归结束再把 list 加入解集,而是在进入子递归之前就把 list 加入解集,在递归压栈时“做事”。
在这里插入图片描述

之前一直搞不懂一个i = 3退出最内层递归的问题。这主要是对自然结束后做了啥事情出了理解问题。
具体展开流程如图。
在这里插入图片描述

class Solution {
private:
	vector<vector<int>> result;
	vector<int> path;
	void backtracking(vector<int>& nums, int startIndex) {
		//Print(path);
		result.push_back(path);
		for (int i = startIndex; i < nums.size(); i++) {
			path.push_back(nums[i]);
			backtracking(nums, i + 1);
			path.pop_back();
		}
	}
public:
	vector<vector<int>> subsets(vector<int>& nums) {
		backtracking(nums, 0);
		return result;
	}
};

总结:

backtracking() {
    if (终止条件) {
        存放结果;
    }

    for (选择:选择列表(可以想成树中节点孩子的数量)) {
        递归,处理节点;
        backtracking();
        回溯,撤销处理结果
    }
}

LeetCode112. 路径总和


大意:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

关于一个二叉树递归遍历要不要返回值的问题:
如果递归搜索了整个二叉树,递归函数就不要返回值。如果只是找到一条符合条件的路径,就要立刻返回这个值。并且用一个变量在外面接着。自顶向下的递归,自下而上的返回。

class Solution {
public:
	bool dfs(TreeNode* root,int sum,int cursum)
	{
		if (root->left == NULL && root->right == NULL && cursum == sum)
			return true;
		else if (root->left == NULL && root->right == NULL)  //两种终止情况
			return false;

		if (root->left)
			if (dfs(root->left, sum, cursum + root->left->val)) //若子树返回true,立刻返回true
				return true;
		if (root->right)
			if (dfs(root->right, sum, cursum + root->right->val)) //和上面一样,结构性解决递归问题  内部的参数解决了回溯问题
				return true;
        return false;
	}

	bool hasPathSum(TreeNode* root, int sum) {
		if (root == NULL) return false;
		return dfs(root, sum, root->val);
	}
};

LeetCode 113.路径总和 2


和112大同小异,不过由于要找出所有的路径,所以需要遍历整个树。因此不要返回值。并且回溯的方式也略有不同。在“我”这个节点上,处理的是两个儿子的情况。

/*易于理解版*/
class Solution {
public:
	vector<int> tmp;
	vector<vector<int>> vec;
	void dfs(TreeNode* root, int sum, int cursum)
	{
		if (root->left == NULL && root->right == NULL && cursum == sum) 
		{
			vec.push_back(tmp);
			return;
		}
		if (root->left == NULL && root->right == NULL)  //以上,找退出条件
			return;
		
		if (root->left)
		{
			tmp.push_back(root->left->val); //"我"这个节点要干的事
			dfs(root->left, sum, cursum + root->left->val); //向左怼到底,每出一层后面就是一次回溯等着
			tmp.pop_back(); //回溯
		}
		
		if (root->right)
		{	
			tmp.push_back(root->right->val);
			dfs(root->right, sum, cursum + root->right->val);//向右怼,如果到底了自然结束这一层递归,回到上一层的左
			tmp.pop_back();
		}
	}
	vector<vector<int>> pathSum(TreeNode* root, int sum) {
		if (root == NULL) return vec;
		tmp.push_back(root->val); //由于代码结构中“我”,只能考虑儿子节点,不能Push自己
		dfs(root, sum, root->val);
		return vec;
	}
};

此外还有一种利用数组脚标来回溯的骚操作。也就是一共有一个tmp临时回溯用数组,和需要进result的path数组。

class Solution {
public:
	vector<int> tmp = vector<int>(9999);//中转用
	vector<vector<int>> vec;
	void dfs(TreeNode* root, int sum, int cursum, int depth)
	{
		tmp[depth] = root->val; //“我”要干的事
		if (root->left == NULL && root->right == NULL)//退出条件
		{
			if (cursum == sum)
			{
				vector<int> path;
				for (int i = 0; i < depth + 1; i++)
					path.push_back(tmp[i]);
				vec.push_back(path);
			}
			else
				return;
		}
		else
		{
			if (root->left)
				dfs(root->left, sum, cursum + root->left->val, depth + 1); //交给结构来递归
			if (root->right)
				dfs(root->right, sum, cursum + root->right->val, depth + 1); //利用depth参数做脚标的回溯
		}
	}

	vector<vector<int>> pathSum(TreeNode* root, int sum) {
		if (root == NULL) return vec;
		dfs(root, sum, root->val, 0);
		return vec;
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值