首页 > 其他分享 >二叉树路径总和系列问题

二叉树路径总和系列问题

时间:2023-12-26 16:34:27浏览次数:33  
标签:node preSumMap process 路径 int targetSum preSum 总和 二叉树

二叉树路径总和系列问题

作者:Grey

原文地址:

博客园:二叉树路径总和系列问题

CSDN:二叉树路径总和系列问题

LeetCode 112. 路径总和

定义递归函数

boolean process(TreeNode node, int preSum, int target)

递归含义表示:从 node 节点一直到树的叶子节点,preSum 表示 node 之前的节点之和,target 表示目标值。主函数调用

public boolean hasPathSum(TreeNode root, int targetSum) {
    return process(root, 0, targetSum);
}

就是要的答案。

接下来是递归函数的 base case,容易得到

if (node == null) {
    // 空节点自然返回false,即永远得不到 target 值
    return false;
}
if (node.left == null && node.right == null) {
    // node 是叶子节点
    // 可以结算了,前面得到的值 + 当前节点值 如果符合目标值,就返回 true
    // 否则返回 false
    return preSum + node.val == target;
}

接下来是普遍情况,当前节点往右走,或者当前节点往左走,如果任何一个满足条件,就返回 true 表示满足,否则返回 false

return process(node.left, preSum + node.val, target) || process(node.right, preSum + node.val, target);

完整代码如下

public boolean hasPathSum(TreeNode root, int targetSum) {
    return process(root, 0, targetSum);
}

// 从 某个节点到 叶子节点,是否可以累计得到某个值(targetSum), 之前的值是 preSum
public boolean process(TreeNode node, int preSum, int target) {
    if (node == null) {
        return false;
    }
    if (node.left == null && node.right == null) {
        // node 是叶子节点
        return preSum + node.val == target;
    }
    return process(node.left, preSum + node.val, target) || process(node.right, preSum + node.val, target);
}

LeetCode 113. 路径总和 II

定义递归函数

void process(TreeNode node, int preSum, List<Integer> path, int targetSum, List<List<Integer>> result)

递归含义表示:从 node 节点一直到树的叶子节点,preSum 表示 node 之前的节点之和,targetSum 表示目标值,path 记录之前的节点轨迹,result 用于收集所有满足条件的 path,主函数调用:

public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return new ArrayList<>();
    }
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    // 从 root 到叶子节点
    // preSum 初始为 0,path 和 result 都初始为空
    process(root, 0, path, targetSum, result);
    return result;
}

递归函数的 base case 如下:

if (node == null) {
    return;
}
if (node.left == null && node.right == null) {
    // 叶子节点
    if (preSum + node.val == targetSum) {
        path.add(node.val);
        result.add(path);
    }
    return;
}

说明:针对叶子节点(即:node.left == null && node.right == null),如果满足条件preSum + node.val == targetSum,则找到一条满足条件的路径,使得结果之和等于目标值,此时 result 开始收集result.add(path)

接下来是普遍情况,为了避免 path 在走左侧和走右侧的时候被污染,所以拷贝出两个 path 的副本来进行递归调用

// 走左侧,用copy1
List<Integer> copy1 = new ArrayList<>(path);
// 走右侧,用copy2
List<Integer> copy2 = new ArrayList<>(path);

copy1.add(node.val);
copy2.add(node.val);

接下来调用递归函数

process(node.left, preSum + node.val, copy1, targetSum, result);
process(node.right, preSum + node.val, copy2, targetSum, result);

完整代码如下

public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return new ArrayList<>();
    }
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    process(root, 0, path, targetSum, result);
    return result;
}

public void process(TreeNode node, int preSum, List<Integer> path, int targetSum, List<List<Integer>> result) {
    if (node == null) {
        return;
    }
    if (node.left == null && node.right == null) {
        // 叶子节点
        if (preSum + node.val == targetSum) {
            path.add(node.val);
            result.add(path);
        }
        return;
    }
    List<Integer> copy1 = new ArrayList<>(path);
    List<Integer> copy2 = new ArrayList<>(path);
    copy1.add(node.val);
    copy2.add(node.val);
    process(node.left, preSum + node.val, copy1, targetSum, result);
    process(node.right, preSum + node.val, copy2, targetSum, result);
}

LeetCode 437. 路径总和 III

这一题和上一个 Path Sum II 问题最大的区别是:这里不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。返回的结果也只需要给出路径的条数,不需要给出具体的路径情况。

定义递归函数

int process(TreeNode node, int targetSum, long preSum, HashMap<Long, Integer> preSumMap)

递归含义表示:从 node 到结尾,前面累计的结果是 preSum,前面组成的路径中,每个路径之和的结果有多少种,存在 preSumMap 中(即:preSum(i, j) 表示 前面路径的累加和为 i 的路径有 j 种),返回得到 targetSum 的路径数有多少种。

所以主函数调用

process(root, targetSum, 0, preSumMap);

即为答案,先看递归函数完整代码

// 返回方法数
// 从 node 到结尾,前面累计的结果是 preSum,前面组成的路径中,每个路径之和的结果有多少种,存在 preSumMap
// preSum(i,j) 表示 前面路径的累加和为 i 的路径有 j 种
public int process(TreeNode node, int targetSum, long preSum, HashMap<Long, Integer> preSumMap) {
    if (node == null) {
        return 0;
    }
    long all = preSum + node.val;
    int ans = 0;
    if (preSumMap.containsKey(all - targetSum)) {
        // 说明之前有路径可以得到
        ans = preSumMap.get(all - targetSum);
    }
    // 登记当前结果到preSumMap中
    if (!preSumMap.containsKey(all)) {
        preSumMap.put(all, 1);
    } else {
        preSumMap.put(all, preSumMap.get(all) + 1);
    }
    ans += process(node.left, targetSum, all, preSumMap);
    ans += process(node.right, targetSum, all, preSumMap);
    // 清理现场
    if (preSumMap.get(all) == 1) {
        preSumMap.remove(all);
    } else {
        preSumMap.put(all, preSumMap.get(all) - 1);
    }
    return ans;
}

其中

if (node == null) {
    return 0;
}

是 base case,空树,返回 0 种方法数,

接下来:

long all = preSum + node.val;
int ans = 0;
if (preSumMap.containsKey(all - targetSum)) {
    // 说明之前有路径可以得到
    ans = preSumMap.get(all - targetSum);
}

表示,想得到目标值 targetSum,而当前的累加和是 all,说明 preSumMap 中必须存着累加和为(all - targetSum)的记录。
假设之前(all - targetSum)结果的路径有 n 条,那么结果至少是 n。

接下来:

// 登记当前结果到preSumMap中
if (!preSumMap.containsKey(all)) {
    preSumMap.put(all, 1);
} else {
    preSumMap.put(all, preSumMap.get(all) + 1);
}

表示登记当前之和为 all 的记录到 preSumMap 中,再接下来:

ans += process(node.left, targetSum, all, preSumMap);
ans += process(node.right, targetSum, all, preSumMap);

表示去左右树收集答案,累加到 ans 中,最后:

// 清理现场
if (preSumMap.get(all) == 1) {
    preSumMap.remove(all);
} else {
    preSumMap.put(all, preSumMap.get(all) - 1);
}

递归函数清理现场,常规操作。

特别注意,由于递归函数中

long all = preSum + node.val;
int ans = 0;
if (preSumMap.containsKey(all - targetSum)) {
    // 说明之前有路径可以得到
    ans = preSumMap.get(all - targetSum);
}

这里判断条件是preSumMap.containsKey(all - targetSum),而当 preSumMap 一个元素也没有的时候,可以组成和为 0L 的路径,所以,在主函数调用的时候,需要增加一句

preSumMap.put(0L, 1);

完整代码如下

public int pathSum(TreeNode root, int targetSum) {
    HashMap<Long, Integer> preSumMap = new HashMap<>();
    // 累加和为0的情况,默认就有一种了(空树)
    preSumMap.put(0L, 1);
    return process(root, targetSum, 0, preSumMap);
}

// 返回方法数
// 从 node 到结尾,前面累计的结果是 preSum,前面组成的路径中,每个路径之和的结果有多少种,存在 preSumMap
// preSum(i,j) 表示 前面路径的累加和为 i 的路径有 j 种
public int process(TreeNode node, int targetSum, long preSum, HashMap<Long, Integer> preSumMap) {
    if (node == null) {
        return 0;
    }
    long all = preSum + node.val;
    int ans = 0;
    if (preSumMap.containsKey(all - targetSum)) {
        // 说明之前有路径可以得到
        ans = preSumMap.get(all - targetSum);
    }
    // 登记当前结果到preSumMap中
    if (!preSumMap.containsKey(all)) {
        preSumMap.put(all, 1);
    } else {
        preSumMap.put(all, preSumMap.get(all) + 1);
    }
    ans += process(node.left, targetSum, all, preSumMap);
    ans += process(node.right, targetSum, all, preSumMap);
    // 清理现场
    if (preSumMap.get(all) == 1) {
        preSumMap.remove(all);
    } else {
        preSumMap.put(all, preSumMap.get(all) - 1);
    }
    return ans;
}

更多

算法和数据结构学习笔记

算法和数据结构学习代码

标签:node,preSumMap,process,路径,int,targetSum,preSum,总和,二叉树
From: https://www.cnblogs.com/greyzeng/p/15700243.html

相关文章

  • LiveGBS流媒体平台GB/T28181常见问题-配置国标流媒体服务日志文件个数及记录时长配置l
    LiveGBS流媒体平台GB/T28181常见问题-如何配置国标流媒体服务日志文件个数及记录时长1、日志文件2、配置日志文件个数及记录时间3、配置日志文件路径4、相关问题4.1、如何关闭信令日志?5、搭建GB28181视频直播平台1、日志文件部署LiveGBS后,LiveCMS和LiveSMS的解压目录下都个l......
  • 【LeetCode】39. 组合总和
    题目给你一个无重复元素的整数数组candidates和一个目标整数target,找出candidates中可以使数字和为目标数target的所有不同组合,并以列表形式返回。你可以按任意顺序返回这些组合。candidates中的同一个数字可以无限制重复被选取。如果至少一个数字的被选数......
  • 三一集团产品负责人谢庚曦:AI重塑HR数智化的实现路径
    在12月15日举办的用友大易2023人才管理与HR数智化年度论坛中,三一集团HR产品负责人谢庚曦先生分享了三一集团HR数智化转型AI技术在各个场景中的实践,为参会者带来了可借鉴的经验。以下内容根据谢庚曦先生演讲内容整理而成。三一集团HR数智化背景三一集团成立于1989年,主营业务是以“工......
  • GScan v0.1 被攻击入侵后 溯源 安全应急响应 Linux主机排查 实现主机侧Checklist的自
    GScanv0.1本程序旨在为安全应急响应人员对Linux主机排查时提供便利,实现主机侧Checklist的自动全面化检测,根据检测结果自动数据聚合,进行黑客攻击路径溯源。CheckList检测项自动化程序的CheckList项如下:1、主机信息获取2、系统初始化alias检查3、文件类安全扫描3.1、系统重要文......
  • 算法题:剑指 Offer 07. 重建二叉树(题目+思路+代码+注释)时空时间优先选O(N) O(N) 2ms击
    题目剑指Offer07.重建二叉树输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。示例1:Input:preorder=[3,9,20,15,7],inorder=[9,3,15,20,7]Output:[3,9,20,null,null,15,7]示例2:Input:......
  • 树与二叉树与森林
    2、若将一棵树T转化为对应的二叉树BT,则下列对BT的遍历中,其遍历序列与T的后根遍历序列相同的是______。A.先序遍历B.中序遍历C.后序遍历D.按层遍历解析:在后根遍历(也称为后序遍历或后序遍历)中,对于T的每个节点,首先遍历其左子树,然后遍历其右子树,最后访问该节点本身。而在中根......
  • MFC---路径格式化
    在路径字符串格式化时,通常需要使用双反斜杠\而不是单斜杠//。在文件路径中,Windows系统通常使用反斜杠\作为路径分隔符。问题出现问题原因问题解决......
  • 二叉树 - 基本概念
    1.树的基本概念与数组链表不同,树是一种非线性的存储结构,它由n(n>=0)个节点构成并具有层次关系的存储结构把这个存储结构叫做树是因为它看上去像一颗倒挂着的树,只是根在上叶子在下它有以下特性:1. 有一个特殊的结点,称为根结点,根结点没有前驱结点2.树是由若干不相交的......
  • 完全二叉树的公共父结点
    1.有点后序遍历的思想,就是先把左子树,右子树的结果算出来,然后合并到根节点。2.合并时四种情况分类讨论.3.对于遇到要找的点就可以直接返回,不管另一个点在这个点下面还是在别的子树上,都是正确的intn,m;inta[N];intquery(introot,intx,inty){ //cerr<<root<<endl; if(r......
  • Spring工具类--路径匹配(AntPathMatcher)--使用/实例
    原文网址:Spring工具类--路径匹配(AntPathMatcher)--使用/实例_IT利刃出鞘的博客-CSDN博客简介整个Spring(SpringBoot)框架的路径解析都是按照Ant的风格来的,比如:Controller的请求路径、文件路径、包的路径。所以,掌握Ant的路径匹配很重要。Spring中的具体实现:org.springframewor......