LeetCode 二叉树 437. 路径总和 III
437. 路径总和 III
问题描述
给定一个二叉树的根节点 root 和一个整数 targetSum,求该二叉树中节点值之和等于 targetSum 的路径数目。路径定义如下:
- 路径不需要从根节点开始。
- 路径不需要在叶子节点结束。
- 路径方向必须是向下的(只能从父节点到子节点)
示例
- 示例 1
- 输入:
root = [10,5,-3,3,2,null,11,3,-2,null,1]
targetSum = 8 - 输出:
3 - 解释:
和等于 8 的路径有 3 条(具体路径可通过树结构推导,此处略图)。
- 输入:
- 示例 2
- 输入:
root = [5,4,8,11,null,13,4,7,2,null,null,5,1]
targetSum = 22 - 输出:
3 - 解释:
和等于 22 的路径有 3 条。
- 输入:
题解
方法一:暴力枚举
根据题目,我们需要求出二叉树中一段路径的数字的和,判断是否 == targetSum
求一段数字的和可以用 前缀和 实现,避免重复枚举加和计算
先来看一段路径的情况,假设二叉树只有一条路径
那么此时问题可以转换为:求一给定数组中的连续子数组的和 == targetSum的个数
处理得到前缀和数组
之后枚举连续子数组的结尾,之后在遍历所有可能的开头,暴力寻找
那么对于有多条路径的二叉树
使用 vector<int> pre_sum记录下当前路径的前缀和数组
dfs 遍历二叉树
那么在遍历二叉树的过程中
每一个遇到的节点,都是一条路径中的一份子
那么意味着在 dfs 遍历树的过程中,其实就是遍历所有路径
所以遍历的过程,就是上述枚举连续子数组的结尾
于是对于每一个节点,遍历到它相当于将其作为结尾
接着枚举它的开头,也就是枚举前缀和数组vector<int> pre_sum,寻找到 target_Sum 即可
由于vector<int> pre_sum记录的当前路径的前缀和,当此节点递归完毕之后,需要对其进行回溯,防止影响其他路径
代码如下↓
class Solution {
public:int pathSum(TreeNode* root, int targetSum) {// 初始化前缀和数组,记录当前路径的累计和vector<int> pre_sum;// 当前路径的总和(使用long long防止溢出)long long sum=0;// 结果计数器,记录满足条件的路径数int res=0;// 定义DFS lambda函数(递归遍历二叉树)auto dfs = this auto&& dfs,TreeNode* p -> void {// 递归终止条件:当前节点为空时返回if(!p) {return ;}// 更新路径总和:将当前节点值加入累计和sum += p->val;// 将当前总和加入前缀和数组(记录路径历史)pre_sum.push_back(sum);// 检查路径和:从根节点到当前节点的总和是否等于targetSumif(sum == targetSum) {res += 1; // 满足条件则计数}// 枚举所有可能的路径开头(除当前节点自身)int n = pre_sum.size();for(int i = 0; i < n - 1; i++) {// 计算子路径和:从开头节点到当前节点的和(即sum - pre_sum[i])if(sum - pre_sum[i] == targetSum) {res += 1; // 满足条件则计数}}// 递归遍历左子树(深度优先)dfs(p->left);// 递归遍历右子树(深度优先)dfs(p->right);// 回溯:移除当前节点的贡献,确保不影响其他路径sum -= p->val;pre_sum.pop_back(); // 从前缀和数组中弹出当前总和};// 从根节点开始DFS遍历dfs(root);return res; // 返回最终结果}
};
枚举开头的时候,前缀和数组相减最多得到不包含开头的连续子数组,所以要么向前缀和数组开头插入 0 ,或对此时总和
sum单独判断
并且由于
target_Sum可以是 0 ,所以如果先更新的pre_sum数组,不能枚举到结尾,也就是路径不能是空的,空路径不能当做是总和 = 0
方法二:哈希表
对于一段结尾确定且为数组最后数据的连续子数组
相当于将数组分为了左右两部分
我们需要右边部分和为 target_Sum
那么左边部分和就为 数组总和sum - target_Sum
于是可以用哈希表hash_sum记录所有左边部分总和的值
对于每一个结尾,去查找哈希表中的个数即可
这样省去了枚举开头的过程
代码如下↓
class Solution {
public:int pathSum(TreeNode* root, int targetSum) {// 哈希表记录路径前缀和及其出现次数unordered_map<long long,int> hash_sum;// 初始化:空路径前缀和为0(处理子数组为整个数组的情况)hash_sum[0]=1; long long sum=0; // 当前递归路径的累计和int res=0; // 满足条件的路径计数// DFS递归遍历二叉树(C++17显式this捕获lambda)auto dfs = this auto&& dfs,TreeNode* p -> void {if(!p) return; // 空节点终止递归sum += p->val; // 更新当前路径和// 关键步骤:先查询满足条件的子数组数量// 当前sum-targetSum表示需要的左侧部分和res += hash_sum[sum - targetSum]; // 后更新哈希表(避免包含空路径)hash_sum[sum] += 1; // 递归遍历左右子树dfs(p->left);dfs(p->right);// 回溯:撤销当前节点的路径和记录hash_sum[sum] -= 1; sum -= p->val; // 恢复路径和};dfs(root); // 从根节点启动DFSreturn res; // 返回目标路径总数}
};
和方法一中同样的问题,需要额外考虑子数组就是整个数组的情况,此时左边部分的总和就是0,所以哈希表
hash_sum[0]=1
依旧和方法一相同的问题,不存在空路径,所以不能先更新哈希表
hash_sum[sum]+=1,而是要先计算完满足条件的子数组后再更新
