【算法训练-二叉树 六】【路径和计算】路径总和I、路径总和II、路径总和III、二叉树的最大路径和

简介: 【算法训练-二叉树 六】【路径和计算】路径总和I、路径总和II、路径总和III、二叉树的最大路径和

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【二叉树的节点查找】,使用【二叉树】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。

明确目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍

路径总和I【EASY】

先来个初级版本的,只需要确定是否存在这样的路径和

题干

解题思路

既然是检查从根到叶子有没有一条等于目标值的路径,那肯定需要从根节点遍历到叶子,我们可以在根节点每次往下一层的时候,将sum减去节点值,最后检查是否完整等于0. 而遍历的方法我们可以选取二叉树常用的递归前序遍历,因为每次进入一个子节点,更新sum值以后,相当于对子树查找有没有等于新目标值的路径,因此这就是子问题,递归的三段式为

  • 终止条件: 每当遇到节点为空,意味着过了叶子节点,返回。每当检查到某个节点没有子节点,它就是叶子节点,此时sum减去叶子节点值刚好为0,说明找到了路径。
  • 返回值: 将子问题中是否有符合新目标值的路径层层往上返回。
  • 本级任务: 每一级需要检查是否到了叶子节点,如果没有则递归地进入子节点,同时更新sum值减掉本层的节点值。

代码实现

给出代码实现基本档案

基本数据结构二叉树

辅助数据结构

算法递归、DFS

技巧

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

import java.util.*;
/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param root TreeNode类
     * @param sum int整型
     * @return bool布尔型
     */
    public boolean hasPathSum (TreeNode root, int sum) {
        if (root == null) {
            return false;
        }
        return dfsFind(root, sum);
    }
    private boolean dfsFind(TreeNode node, int sum) {
        // 1 递归终止条件,节点为空,意味着越过了叶子节点,空节点没有路径
        if (node == null) {
            return false;
        }
        // 2 本级任务,减去当前节点的值,sum为接下来子树的目标值,同时判断是否到达叶子节点
        sum = sum - node.val;
        if (node.left == null && node.right == null) {
            return sum == 0 ? true : false;
        }
        // 3 返回值,返回子树的判断结果
        return dfsFind(node.left, sum) || dfsFind(node.right, sum);
    }
}

复杂度分析

时间复杂度:O(n),其中n为二叉树的节点数,遍历整棵二叉树

空间复杂度:O(n),最坏情况下,二叉树化为链表,递归栈深度最大为n

路径总和II【MID】

难度升级,需要给出所有满足条件的节点结果

题干

解题思路

我们可以采用深度优先搜索的方式,枚举每一条从根节点到叶子节点的路径。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。主要思路同上,需要注意的是path的引用性质,每次探索完新的路线后需要回溯

代码实现

给出代码实现基本档案

基本数据结构二叉树

辅助数据结构

算法递归、DFS

技巧

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

class Solution {
    // 1 设置结果集,并使用栈来记录完整路径
    List<List<Integer>> result = new LinkedList<List<Integer>>();
    Stack<Integer> path = new Stack<Integer>();
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        dfsFindPath(root, targetSum);
        return result;
    }
    public void dfsFindPath(TreeNode root, int targetSum) {
        // 1 如果越过叶子节点,则不满足条件
        if (root == null) {
            return;
        }
        // 2 路径添加当前节点值
        path.push(root.val);
        // 3 路径经过本级节点后目标值
        targetSum = targetSum - root.val;
        // 4 如果到达叶子节点,则进行判断,如果满足条件,则为寻找的目标路径
        if (root.left == null && root.right == null && targetSum == 0) {
            result.add(new LinkedList<Integer>(path));
        }
        // 5 没有找到,递归的从左右子树找
        dfsFindPath(root.left, targetSum);
        dfsFindPath(root.right, targetSum);
        // 6 因为path是引用类型,所有到达叶子节点无论是否找到,都要退出当前路径进入自己上层路径进行新的路径搜素哦
        path.pop();
    }
}

复杂度分析

时间复杂度:O(n),其中n为二叉树的节点数,遍历整棵二叉树

空间复杂度:O(n),最坏情况下,二叉树化为链表,递归栈深度最大为n

路径总和III【MID】

难度再升级,这次不需要在叶子节点结束了,也不需要从根节点开始了

题干

解题思路

使用前缀和解决本问题

为什么要用前缀和

什么是前缀和,二叉树中,一个节点的前缀和就是该节点到根之间的路径和

1
     /  \
    2    3
   / \    \
  4   5    6      节点4的前缀和为:1 + 2 + 4 = 7
 / \   \
7   8   9         节点8的前缀和:1 + 2 + 4 + 8 = 15  节点9的前缀和:1 + 2 + 5 + 9 = 17

题目要求的是找出路径和等于给定数值的路径总数, 而两节点间的路径和 = 两节点的前缀和之差

假如题目给定数值为5
                     1     节点1的前缀和为: 1
                    / 
                   2    
                  / 
                 3         节点3的前缀和为: 1 + 2 + 3 = 6
                / 
               4  
prefix(3) - prefix(1) == 5,  所以 节点1 到 节点3 之间有一条符合要求的路径( 2 --> 3 )

所以我们只用遍历整颗树一次,记录每个节点的前缀和,并查询该节点的祖先节点中符合条件的个数,将这个数量加到最终结果上

HashMap存什么

HashMap的key是前缀和, value是该前缀和的路径数量,记录数量是因为同一个前缀和有不同路径的可能。

1    前缀和为1
     / 
    0      前缀和为1
   /
  2        前缀和为3
 路径和为2的路径总数有:prefix(2) - prefix(1) 对应路径:0-2
                       prefix(2) - prefix(0) 对应路径:2

恢复状态的意义

由于题目要求:路径方向必须是向下的(只能从父节点到子节点)当我们讨论两个节点的前缀和差值时,有一个前提:一个节点必须是另一个节点的祖先节点,换句话说,当我们把一个节点的前缀和信息更新到map里时,它应当只对其子节点们有效。举个例子,下图中有两个值为2的节点(A, B)。

0
     /  \
    A:2  B:2
   / \    \
  4   5    6
 / \   \
7   8   9

当我们遍历到最右方的节点6时,对于它来说,此时的前缀和为2的节点只该有B, 因为从A向下到不了节点6(A并不是节点6的祖先节点)。如果我们不做状态恢复,当遍历右子树时,左子树中A的信息仍会保留在map中,那此时节点6就会认为A, B都是可追溯到的节点,从而产生错误。状态恢复代码的作用就是: 在遍历完一个节点的所有子节点后,将其从map中除去。

左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和去除。避免回溯之后影响上一层。因为思想是前缀和,不属于前缀的,我们就要去掉它

代码实现

给出代码实现基本档案

基本数据结构二叉树

辅助数据结构哈希

算法递归、DFS

技巧前缀和

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 1 定义前缀和Map,key为前缀和,value为该前缀和的路径总数
    private Map<Long, Integer> prefixMap = new HashMap<Long, Integer>();
    // 2 定义结果集
    private Integer result = 0;
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }
        //  这是一个初态,一个节点到根节点的路径和就是节点的前缀和减去0,没有这个的话所有到根节点的符合条件的路径都无法找到,
        //  例如:1-3,找到路径和为4的,此时preSum(3)-4=preSum(0),如果没有preSum(0)为1条路径,就1条都招不到了
        prefixMap.put(0L, 1);
        dfsFindPath(root, 0L, targetSum); //传入参数是根节点root,和root之前的前缀和0。
        return result;
    }
    // 递归查找满足条件的路径和
    private void dfsFindPath(TreeNode node, long  prefixSum, int targetSum) {
        // 1 查找到空节点,返回
        if (node == null) {
            return;
        }
        // 2 记录当前节点前缀和
        prefixSum = prefixSum + node.val;
        // 如果之前出现过符合条件的前缀和,则记录+N
        result = result + prefixMap.getOrDefault(prefixSum - targetSum, 0);
        // 更新路径上当前节点前缀和的个数:把当前节点的前缀和也加入到哈希表中,如果已经存在了就给value + 1
        prefixMap.put(prefixSum, prefixMap.getOrDefault(prefixSum, 0) + 1);
        // 3 再去遍历其左右子树继续记录前缀和并找可行路径。
        dfsFindPath(node.left, prefixSum,targetSum); 
        dfsFindPath(node.right, prefixSum, targetSum);
        // 4 当自己的子树都寻找完毕后,需要把本级的前缀和去掉,返回上一层级
        prefixMap.put(prefixSum, prefixMap.get(prefixSum) - 1); 
    }
}

复杂度分析

时间复杂度:O(n),其中n为二叉树的节点数,遍历整棵二叉树

空间复杂度:O(n),最坏情况下,哈希表存储N个元素,递归栈深度为N

二叉树的最大路径和【HARD】

难度再升级,这次要获取和为最大的路径

题干

解题思路

这题和通过递归最大深度求二叉树的直径有异曲同工之妙

首先,考虑实现一个简化的函数 maxGain(node),该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大具体而言,该函数的计算如下。

  • 空节点的最大贡献值等于 0。
  • 非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)。

例如,考虑如下二叉树。

-10
   / \
  9  20
    /  \
   15   7

这样我们就可以在寻找最大贡献值分支的路上,不断更新最大路径和

代码实现

给出代码实现基本档案

基本数据结构二叉树

辅助数据结构哈希

算法递归、DFS

技巧前缀和

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 1 设置最大路径总和并初始化
    private int maxSum = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
        if (root == null) {
            return 0;
        }
        dfsFindBestPath(root);
        return maxSum;
    }
    // 返回贡献值较大的节点总和
    private int dfsFindBestPath(TreeNode node) {
        // 1 到达空节点,越过叶子节点,贡献为0
        if (node == null) {
            return 0;
        }
        // 2 计算当前节点总值,分别获取左右子树贡献值,如果和为负数,则舍弃
        int leftValue = Math.max(dfsFindBestPath(node.left), 0);
        int rightValue = Math.max(dfsFindBestPath(node.right), 0);
        int curSumValue = node.val + leftValue + rightValue;
        // 3 maxSum与当前节点总值比较并重设最大值
        maxSum = Math.max(maxSum, curSumValue);
        // 4 设置当前节点最大值并返回,因为路径不能两边都走
        return node.val + Math.max(leftValue, rightValue);
    }
}

复杂度分析

时间复杂度:O(n),其中n为二叉树的节点数,遍历整棵二叉树

空间复杂度:O(n),最坏情况下,二叉树化为链表,递归栈深度最大为n

拓展知识:前缀和的使用

前缀和在二叉树中的应用通常用于解决与路径和相关的问题,尤其是在查找二叉树中的特定路径和或与路径和相关的查询时非常有用。以下是前缀和在二叉树中的应用示例:

  1. 二叉树路径和问题:给定一个二叉树和一个目标和,要求确定是否存在一条从根节点到叶子节点的路径,使得路径上所有节点的值之和等于目标和。前缀和可以用于辅助解决这个问题。你可以从根节点开始,递归计算到每个节点的路径和,并将路径和存储在一个前缀和数组中。然后,当你到达叶子节点时,可以检查前缀和数组中是否存在一个值等于目标和的前缀和,从而确定是否存在符合条件的路径。
  2. 子树路径和问题:与上面的问题类似,但是不要求路径必须从根节点开始。前缀和可以用于记录从根节点到每个节点的路径和,然后在查找子树路径和时,只需计算子树根节点到目标节点的路径和,并与前缀和数组中的值进行比较。
  3. 路径和的范围查询:给定一个二叉树和一个范围 [low, high],要求计算所有路径上节点值之和在该范围内的路径数。前缀和可以帮助优化这个问题,你可以从根节点开始递归计算到每个节点的路径和,并在递归过程中检查路径和是否在 [low, high] 范围内,然后将结果累加。
  4. 树中最大路径和问题:要求找到二叉树中的一条路径,使得路径上节点值之和最大。前缀和可以辅助解决这个问题,你可以从根节点开始递归计算到每个节点的路径和,然后在递归过程中维护一个全局变量,用于记录最大路径和。

在这些问题中,前缀和的应用可以帮助减少不必要的计算,提高算法的效率,并使问题更容易解决。它允许你在遍历二叉树的同时,有效地跟踪路径和的信息,以便进行查询和计算。

相关文章
|
2天前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
34 5
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
61 5
|
1月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
48 0
|
1月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
2月前
|
数据采集 监控 安全
厂区地图导航制作:GIS技术与路径导航算法融合
在智能化、数字化时代,GIS技术为厂区的运营管理带来了革命性变化。本文探讨了如何利用GIS技术,通过数据采集、地图绘制、路径规划、位置定位和信息查询等功能,打造高效、精准的智能厂区地图导航系统,提升企业的竞争力和管理水平。
84 0
厂区地图导航制作:GIS技术与路径导航算法融合
|
2月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
80 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
2天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
105 80
|
21天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
7天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。