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;
}
};