112. 路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
- 主要思路:
- 假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val
- 不难发现这满足递归的性质,若当前节点就是叶子节点,那么我们直接判断 sum 是否等于 val 即可(因为路径和已经确定,就是当前节点的值,我们只需要判断该路径和是否满足条件)
- 若当前节点不是叶子节点,我们只需要递归地询问它的子节点是否能满足条件即可。
bool hasPathSum(TreeNode *root, int sum) {
// 特判,输入空树
if(root == null) return false;
// 到叶子节点了
if(!root->left && !root->right) return root->val == sum;
// 否则递归方程伺候
sum = sum - root->val;
# 这个return其实是剪枝了,神来之笔
return hasPathSum(root->left, sum) || hasPathSum(root->right, sum);
}
113.路径总和2
// 113.路径总和2:这个要回溯答案了
vector<vector<int>> pathSum(TreeNode *root, int sum)
{
vector<int> track;
vector<vector<int>> res;
if (root == NULL)
{
return res;
}
dfs2(root, sum, track, res);
return res;
}
void dfs2(TreeNode *root, int sum, vector<int> &track, vector<vector<int>> &res)
{
if (root == NULL)
{
return;
}
if (sum == root->val && root->left == NULL && root->right == NULL)
{
track.push_back(root->val);
res.push_back(track);
track.pop_back();
return;
}
// 这种return会有问题,如果没有满足条件的情况,整个岂不是没有return了
// if(sum == root->val)
// {
// track.push_back(root->val);
// res.push_back(track);
// track.pop_back();
// return;
// }
track.push_back(root->val);
//if (root->left)
dfs2(root->left, sum - root->val, track, res);
//if (root->right)
dfs2(root->right, sum - root->val, track, res);
// sum - root->val系统栈自动回溯
track.pop_back();
}
- 主要思路:
- 先序遍历: 按照 “根、左、右” 的顺序,遍历树的所有节点
- 路径记录: 在先序遍历中,记录从根节点到当前节点的路径。当路径为:
a. 根节点到叶节点形成的路径
b. 各节点值的和等于目标值 sum 时,将此路径加入结果列表
递推工作:- 路径更新: 将当前节点值 root.val 加入路径 path;
- 目标值更新: tar = tar - root.val(即目标值 tar 从 sum 减至 0);
- 路径记录:当 ① root 为叶节点 且 ② 路径和等于目标值 ,则将此路径 path 加入 res。
- 先序遍历: 递归左 / 右子节点
- 路径恢复: == 向上回溯前,需要将当前节点从路径 path 中删除,即执行 path.pop() ==
vector<vector<int>> pathSum(TreeNode* root, int sum) {
recur(root, sum);
}
vector<vector<int>> pathSum(TreeNode* root, int tar){
// 递归出口
if(root == null) return null;
path.push_back(root->val);
// 已经到叶子节点了
if(root->left==null && root->right==null && tar == root->val) res.push_back(path);
tar -= root->val;
// 先序遍历
recur(root->left, tar);
recur(root->right, tar);
// 回溯
path.pop_back();
}
private:
vector<vector<int>> res;
vector<int> path;
加法的写法, 会在private多一个参数:target=sum;
此外这种加法和我之前在p39组合总和的写法有点不一样
上面减法的写法,和这里加法的写法,sum都不需要回溯
原因是,sum 这个变量,是基本类型,它在参数传递的时候的行为是复制本身,每一次传下去都是新的,所以不用撤销
也就是往上回溯时,上一层的那个临时变量sum还在保持原来的值
比较39. 组合
# 在当前层进行相加和
#backtrack(res, track, sum + #candidates[i],i,candidates, target);
# 39 sum +candidates[i]是在下一层进行相加,在下一层先base # case 检查 sum==target, 但是candidates[i]是本层的
# 这两题是有区别的,对于39回溯树来说,每一层有for循环的元素个数
# 而对于树来说,回溯树每一层就是一个左子节点和右子节点
# 本题的方法不能剪枝,必须把整棵树遍历完
tar += root->val;
if (tar == target && root->left == NULL && root->right == NULL)
res.push_back(path);
recur(root->left, tar);
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution
{
public:
vector<vector<int>> pathSum(TreeNode *root, int sum)
{
target = sum;
recur(root, 0);
return res;
}
void recur(TreeNode *root, int tar)
{
// 递归出口
if (root == NULL)
return;
path.push_back(root->val);
tar += root->val; // 遍历到这个层级的树,然后就减,不用等到下一层级再减:而在递归中dfs(tar-root->val),就是在下一层级再减
if (tar == target && root->left == NULL && root->right == NULL)
res.push_back(path);
// 递推方程
recur(root->left, tar);
recur(root->right, tar);
// 回溯:这里pop不止一次,因为在递推方程那里已经递归进去了,所以会pop多次,直到新的一条路径
path.pop_back();
}
private:
vector<vector<int>> res;
vector<int> path;
int target;
};
124.二叉树中的最大路径和
主要思路:
本题中二叉树中的路径和数组中的路径的区别:
数组中路径途径一个节点只能选择来去两个方向
而本题中,比如如图所示,节点b可以有:b->a, b->d,b->e,实际上为3选2的情况
代码如下:
// 124.二叉树中的最大路径和
int maxPathSum(TreeNode *root)
{
dfs2(root);
return res_124;
}
int dfs2(TreeNode *root)
{
if (root == NULL)
return 0;
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
// 即走不走b->d 或 b->e
int left = max(dfs2(root->left), 0); // 此时root->left作为一个子节点往下递归
int right = max(dfs2(root->right), 0);
// 节点b的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
int priceNewpath = root->val + left + right;
// 更新答案
res_124 = max(res_124, priceNewpath);
// 返回节点的最大贡献值
// 当前节点为n,对于当前节点来说,最大值可以为n+left,n+right,n,n+left+right
// 上面四项中的一个,但是返回是不能返回第四个的,那种路径是不成立的
// 因为你要往下递归,所以不能返回不成立的路径
return root->val + max(left, right);
}
private:
int res_124 = INT_MIN;
437. 路径总和 III(双重DFS)
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
- 主要思路:
-
- 先序遍历每个节点,递归1
-
- 每个节点DFS搜索可能的路径,递归2
int ans;
int pathSum(TreeNode* root, int sum) {
// 先序遍历
if(root==null) return ans;
dfs(root, sum);
// 先序遍历, 递归1
// 重点是,在递归1中sum不进行加减,因为sum可能在任意节点开始减而不是之前113.题目从根节点开始
pathSum(root->left, sum);
pathSum(root->right, sum);
return ans;
}
void dfs(TreeNode* root, int sum){
// 递归2
if(root == null) return;
// 递归2是从当前节点往下的一个先序遍历
sum-=root->val;
if(sum==0) ans++;
dfs(root->left, sum);
dfs(root->right, sum);
}