LeetCode 专题灵神算法
二叉树与递归 - 深入理解
| 题目 | 代码 | 备注 |
|---|---|---|
| 104. 二叉树的最大深度 | 代码 | 两种方法 |
| 111. 二叉树的最小深度 | 代码 | *课后作业 |
| 404. 左叶子之和 | 代码 | *课后作业 |
| 112. 路径总和 | 代码 | *课后作业 |
| 129. 求根节点到叶节点数字之和 | 代码 | *课后作业 |
| 1448. 统计二叉树中好节点的数目 | 代码 | *课后作业 |
| 987. 二叉树的垂序遍历 | 代码 | *课后作业 |
题目
104. 二叉树的最大深度
题目链接:求从根节点到最远叶子节点的最长路径包含多少个节点。
示例:输入
root = [3,9,20,null,null,15,7],输出3。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
//使用递归
//归的时候要加自己的深度?
//boudary
if(!root) return 0;
const l = maxDepth(root.left);
const r = maxDepth(root.right);
return Math.max(l,r) + 1;
};习题
111. 二叉树的最小深度
题目链接:求从根节点到最近叶子节点的最短路径包含多少个节点。
示例:输入
root = [3,9,20,null,null,15,7],输出2。
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function(root) {
/**
* 核心思路:宏观递归思维
*
* 1. 把每个子节点都看作一棵子树的根节点。
* 2. 相信 minDepth(root) 能返回当前子树的最小深度。
* 3. 当前层不需要关心子树内部如何计算,
* 只需要拿到左右子树的结果,再组合出自己的答案。
*/
// 0. 递归出口:空树没有根节点,深度为 0
if (root === null) return 0;
/**
* 1. 递阶段:向左右子树索取答案
*
* 假设递归调用能够正确返回左右子树的最小深度。
* 注意:0 也是合法答案,表示当前子树为空。
*
* 调用栈:
* - 当前函数执行到递归调用时,会等待子函数返回。
* - 每次调用都有独立的 root 和局部变量。
* - 子函数返回后,当前函数才继续向下执行。
*/
const leftDepth = minDepth(root.left);
const rightDepth = minDepth(root.right);
/**
* 2. 归阶段:根据左右子树的答案,计算当前树的答案
*
* 情况一:左子树深度为 0(左子树为空)
* 只能从右子树寻找叶子节点。
*
* 情况二:右子树深度为 0(右子树为空)
* 只能从左子树寻找叶子节点。
*
* 注意:前面已经计算过左右子树的深度,
* 不要再次调用 minDepth,否则会重复递归。
*/
if (leftDepth === 0) return rightDepth + 1;
if (rightDepth === 0) return leftDepth + 1;
/**
* 情况三:左右子树都存在
* 取较小深度,再加上当前根节点这一层。
*
* 特殊情况:如果左右子树都为空,说明当前节点是叶子节点。
* 此时 leftDepth 和 rightDepth 都为 0,
* 已经被上面的第一个 if 处理,直接返回 1。
*/
return Math.min(leftDepth, rightDepth) + 1;
};深入最小深度与调用栈
GPT6-medium: 你现在应该能快速判断:
- minDepth(4) 返回 1
- minDepth(2) 返回 2
- minDepth(3) 返回 1
- minDepth(1) 返回 2
1
/ \
2 3
/
4
404. 左叶子之和
题目链接:计算二叉树中所有左叶子节点的值之和。
示例:输入
root = [3,9,20,null,null,15,7],左叶子为9和15,输出24。
- 如何判断一个节点是叶子
- 思路:递归,函数返回sum,递到叶子,再拿和,通过return进行传递
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var sumOfLeftLeaves = function(root) {
// 0. 边界条件:如果不是树了,就返回0
if(root === null) return 0
// 1. 递:计算两个子树的左节点值之和
// 比如站在3的角度,左边9是一个整体,右边20是一个整体,
let sum = sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right)
// 需要想清楚,左叶子,其实也相当于整个树以及多个子树的左叶子;如果我站在叶子节点的角度,那我就不知道我是不是叶子节点
// 所以要在叶子节点的父节点去求和判断
// 2.1 归:正常树的父节点的话,直接return sum了,
// 2.2 归:如果root不是正常树,比如是叶子节点,应当返回0;如果是叶子节点的父节点,应当加上左叶子节点
// 右边叶子节点不需要管
if(!root.left) return sum // 如果当前节点连左孩子都没有,那自然不可能存在「以当前节点为父节点的左叶子」,所以不用额外加任何值,直接返回左右子树已经算好的 sum。
if(root.left.left === null && root.left.right === null) return root.left.val + sum //叶子节点的父节点
// 想象一下结果来反推:sumOfLeftLeaves(3)= 9 + 15;f(9) = 0; f(20) = 15;
return sum
};112. 路径总和
题目链接:判断是否存在一条节点值之和等于 targetSum 的根到叶路径。
示例:输入
root = [5,4,8,11,null,13,4,7,2,null,null,null,1]、targetSum = 22,路径5 → 4 → 11 → 2满足条件,输出true。
- 递的时候,形参带进去了targetSum改完后的实际值,
- 也就是每条路径上每个新建的函数调用栈的targetSum值都是不一样的
- 思路:到达叶子节点的时候进行判断是否为0,那如果是多个路径,return判断是同事进行return吗?不会有先后吗?
- 归的时候看成,往root节点冒泡,传递 true/false,但是最终怎么确定??是有true就true吗?全false就false?
var hasPathSum = function(root, targetSum) {
//思路:路径的话,通过递进的时候形成;每次递进减去值;判断答案,归的时候传递true/false
//节点校验
if(!root) return false;
//减去值
targetSum -= root.val;
//判断叶子节点和答案
if(root.left===null && root.right === null){
return targetSum === 0;
}
//值传递
return hasPathSum(root.left,targetSum) || hasPathSum(root.right,targetSum);
};129. 求根节点到叶节点数字之和
题目链接:把每条根到叶路径上的数字依次拼成整数,并求这些整数之和。
示例:输入
root = [1,2,3],两条路径组成12和13,输出25。
1448. 统计二叉树中好节点的数目
题目链接:统计根到该节点的路径上不存在更大节点值的节点数量。
示例:输入
root = [3,1,4,3,null,1,5],好节点为3、3、4、5,输出4。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var goodNodes = function(root) {
// 思路:dfs遍历,递进维护最大值,归求答案
let dfs = (root, maxnum) => {
// 1. 递归出口,如果不是节点,返回0,父级不要+cnt
if(!root) return 0;
// 2.1 归:叶子节点,照非叶子节点一样判断就行
let cnt = root.val >= maxnum ? 1 : 0;
maxnum = Math.max(maxnum, root.val)
let leftSum = dfs(root.left, maxnum);
let rightSum = dfs(root.right, maxnum);
// 2.2 归:假设两边子节点都返回了子树的好节点个数,那我们直接求和往上归
return cnt + leftSum + rightSum
}
return dfs(root, -Infinity)
};987. 二叉树的垂序遍历
题目链接:按列从左到右、列内从上到下遍历二叉树,同位置的节点按值升序排列。
示例:输入
root = [3,9,20,null,null,15,7],输出[[9],[3,15],[20],[7]]。
评论