【LeetCode刷题笔记】二叉树(二)

本文详细探讨了LeetCode中与二叉树遍历和路径相关的题目,包括二叉树的所有路径、最大路径和、路径总和等。通过DFS和BFS方法,介绍了多种解题策略,如后序遍历、前序遍历和层序遍历,以及递归和回溯等技巧。特别强调了递归函数返回值和答案计算的不同之处,以及在处理路径和时如何处理负值和回溯。文章还涵盖了从根节点到叶节点的路径总和、最长同值路径、翻转二叉树以及二叉树的直径等问题,提供了多种解决方案和代码实现。
摘要由CSDN通过智能技术生成

257. 二叉树的所有路径

解题思路:
  • 1. DFS 前序遍历 ,每次递归将 当前节点的拼接结果 传递到 下一层 中,如果当前节点是 叶子节点 ,就将 当前拼接结果 收集答案并返回。
  • 注意:路径path结果可以使用 String 来拼接,这样可以避免回溯处理。
 

解题思路: 
  • 2. BFS 层序遍历 , 将 父节点拼接结果 子节点 一起 入队 ,在 出队 时判断如果 当前节点是叶子节点 ,就将 当前拼接结果 收集答案 ,否则继续入队。
 

注意,上面代码中容易写错的地方是 path 拼接时,每次拼接的是当前出队节点的值,而不是左右子节点的值。因为访问每个节点时,取出的是父节点的拼接结果,当前节点相对于左右子节点而言就它们的父节点,所以拼接之后传给子节点。

124. 二叉树中的最大路径和

解题思路:
  • 1. DFS 后序遍历 ,定义递归函数 maxSum ,返回 以node为根节点的子树的最大贡献值
  • 贡献值的含义: 以node为根节点的子树对外所能提供的最大路径总和 具体而言,就是 在以node为根节点的子树中寻找 一条 从node出发的路径,使得该路径上的节点值之和最大
  • 1) 空节点 的最大贡献值 =   
  • 2) 非空节点 的最大贡献值 =  节点值  +   max(左孩子的最大贡献值,右孩子的最大贡献值)
  • 3) 如果递归调用子树返回的最大贡献值结果为负,收益不增反降,应该忽略它,记为  0 。( 子节点的最大贡献值为正,才计入,否则不计入
得到每个节点的最大贡献值之后,如何得到二叉树的最大路径和?
  • 对于二叉树中的一个节点,经过该节点的最大路径和 = 该节点的值 +  左孩子的最大贡献值 右孩子的最大贡献值
  • 在递归函数中 后序访问节点 的时机更新答案,对每一个节点都记录一下最大值即可。

注意这里的贡献值和题目所说的路径和的差别:贡献值是从当前节点出发到任意子节点的路径和的最大值,当前节点只能选择左右孩子之一进行连接,而题目所说的路径和是可以经过当前节点的,并且与左右孩子可以连接在一起的。

 

说白了 这个题就是要转换一下思路,先求从每个根节点出发的最大路径和,然后题目所求的经过每个节点的最大路径和就很好求了,就是把自身和左右孩子的最大路径和串联起来即可。对每个根节点求得的最大路径和进行记录比较max值就是答案,所以需要在递归返回值之前更新答案,也就是在后序回溯的地方。 

我觉得这个方法的难点就是递归函数的返回值并不就是所求答案,它是把当前节点与左右之一进行串联,而答案是要把当前节点与左右一起进行串联。 这就导致了递归函数的返回值计算方式与答案的计算方式是两种方式,会有点别扭。(因为在一般的题目中递归函数的返回值往往就是答案)

解题思路:
  • 2. DFS 后序遍历 求从 根节点出发的最大路径和 ,在 后序访问根节点 的时机更新答案。
  • 在递归调用左右子孩子返回之后,反回当前node节点出发的最大路径和,就是以下三种情况的最大值:
  •   1) 根节点值 连上 从左孩子为根节点出发能得到的最大路径和  
  •   2)  根节点值 连上  从右孩子为根节点出发能得到的最大路径和  
  •   3) 根节点自身的值 就是最大和
  • 有了DFS函数返回值之后,题目求的是任意节点出发得到的最大路径和,也就是可以经过根节点也可以不经过, 那么最大路径和有以下几种情况:
  • 1) 当前节点就是最大值 ,经过其他任何子节点都会变小 (可以想象只有根节点为正,其他都为负)
  • 2) 最大路径和 只存在于左子树中 ,不经过当前节点  (可以想象只有根节点为 ,其他都为 )
  • 3) 最大路径和 只存在于右子树中 ,不经过当前节点  (可以想象只有根节点为 ,其他都为 )
  • 4) 当前节点连接上从左子树 出发的最大路径和,即从当前出发往左扎最大
  • 5) 当前节点连接上从右子树 出发的最大路径和,即从当前出发往右扎最大        
  • 6) 当前节点和从左、右子树 出发的最大路径和连接起来最大  
  • 以上6种情况pk出一个最大值,就是 题目所求 的答案

 

从这个代码可以看出方法2和方法1的递归函数整体结构是类似的,只有在递归函数的返回值和答案的计算方式上不同。方法1更像是简化版的,而方法2更像是枚举所有情况版本的。

我们对比一下这两种方法的区别:

对于递归函数的返回值的计算方式:

  • 方法1:由于左右子递归返回值之后忽略了负数(记为0),所以最终返回值只需要当前节点与左右返回值中的较大值相连即可。
  • 方法2:左右子递归返回值没有忽略负数,有正有负,所以最终返回值必须在【当前节点】、【当前节点 + 左子递归返回值】、【当前节点 + 右子递归返回值】三者中决出一个最大值。

对于答案的计算方式:

  •  方法1:由于左右子递归返回值之后忽略了负数(记为0),所以答案只需将当前节点与左右子递归返回值相连即可。
  • 方法2:左右子递归返回值没有忽略负数,有正有负,所以答案需要枚举所有可能情况来决出一个最大值。

这样看来,方法1之所以代码简单,是因为使用了一个关键技巧就是将左右子树返回的结果负值当成0来看待,这样就简化了好多情况。

我个人觉得虽然方法1代码比较简化,但是一般很难想的出来,方法2虽然代码多一点,但是却更加容易分析和理解。(在面试中,如果时间足够,方法2是可以一点一点分析完整的,但是方法1如果你没有做过此题,必然想不到)

112. 路径总和

解题思路:
  • 1. DFS 前序遍历 做加法 前序访问根节点 时, 每次累加当前节点的值作为 路径和 不断往下传, 如果当前节点是 叶子节点 并且 当前的路径和 == target,则返回 true ,否则返回 false
  • 递归终止条件: 空树 返回 false ,说明 跨过了叶子节点也没有找到
  • 最后递归函数的返回值:递归调用左孩子的结果 || 递归调用右孩子的结果。 

当然这里递归函数中也可以不返回值,使用一个全局变量保存结果也可以。 

解题思路: 
  • 2. DFS 前序遍历 做减法 ,将 targetSum 向下传递,每次 前序访问根节点 的时候,从 target中减去当前节点的值 ,作为 新的target 向下传,如果当前节点是 叶子节点 并且 target==0 ,则找到一个答案。否则返回递归调用左右子孩子的结果。

113. 路径总和 II

解题思路: 
  • 1. DFS 前序遍历 做减法 同112,只不过112是求是否存在,这题是要输出所有符合的路径,递归函数使用一个 path 参数记住访问的 每个节点 ,当遇到 叶子节点 target减到0 时,收集答案,将 path 添加到总结果集中。
  • 注意: path 参数如果使用 List 来传,在左右 子递归返回之后 需要 回溯处理 移除最后一个 ,如果使用 String 类型来传path则不需要回溯处理。为了在回溯时方便的移除最后一个元素, path 也可以使用双端队列 Deque (只需 pollLast 即可)。另外,在 收集答案 时需要 new一个新的ArrayList ,否则会因为回溯操作导致结果不正确。 

注意,这里收集答案之后不要加 return 返回,因为如果返回了的话,后面的回溯操作就会执行不到,会影响上一层的结果。如果一定要加 return,可以在 return 之前进行一次回溯操作:

如果觉得回溯处理比较麻烦,path 可以直接使用 String 来拼接,只不过在最后收集答案的时候稍微麻烦一点:

 

解题思路: 
  • 2. DFS 前序遍历 做加法 同112, 递归函数多传一个 path 参数记录访问的路径上的节点 ,注意回溯处理。
解题思路:  
  • 3. BFS 层序遍历,做加法 同257, 当前节点、累加和、访问路径 三者封装在一起,一同入队和出队,当出队元素为 叶子节点 累加和==target 时,收集答案。
  • 注意: path 使用 List 类型每次往下传时需要 new一个 新的 ArrayList

这里也可以不保存path,可以使用一个map提前保存所有节点的父节点,当访问到叶子节点需要添加path时,利用map遍历从当前节点到根节点的路径即可。参考代码如下:

注意,像 LinkedList 这样的双端队列当作栈使用时,都是向 First 那端压栈的,因此最后 LinkedList 中从前往后排的就是从根节点到当前节点的路径。

解题思路:  
  • 4. BFS 层序遍历,做减法 ,同方法3, 当前节点、 target 、访问路径 三者封装在一起入队和出队, target减到0时,收集答案。

437. 路径总和 III

解题思路:  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值