题目
给你一棵二叉树的根节点
root,请你返回 层数最深的叶子节点的和 。
- 示例1
输入: root = [1,2,3,4,5,null,6,7,null,null,null,null,8] 输出: 15
- 示例2
输入: root = [6,7,8,2,7,1,3,9,null,1,4,null,null,null,5] 输出: 19
- 难度:中等
- 使用语言:TypeScript
- 链接:层数最深叶子节点的和
虽然这是一道难度为中等的题目,不过相比于昨天,我感觉更好做一些,因为题目很好理解🐶。
中等的类型的题目大多会有一个套路,同一个套路的题目解题思路基本上大同小异,就和以前解数学题目一样,用一个基本的解题思路,然后根据题目的不同,做出一点改变。
二叉树类型的题目首先得想到他的遍历方法:前序遍历、中序遍历、后续遍历还有广度优先和深度优先遍历。
虽然深度优先和前序遍历得到的数据顺序是一样的,但是代码思路略微有所不同。
思路一:广度优先遍历
这道题目是要我们求出最深叶子结点的和,首先就可以想到广度优先遍历的方法。广东优先得到的遍历顺序是1-2-3-4-5-6-7-8,并且可以把每一层的数据分开来,先用JavaScript写一下广度优先的大体思路
// 广度优先(二叉树)
function bfs(node) {
let q = [], // 暂寸每一层的数据
step = 0 //记录层数
q.push(node)
while (q.length) {
const size = q.length
for (let i = 0; i < size; i++) {
const cur = q.shift()
console.log(cur)
if (cur.left != null) {
q.push(cur.left)
}
if (cur.right != null) {
q.push(cur.right)
}
}
step++
}
}
- 大致思路是把每一层的数据都存到q里,每次循环取出数组q的第一个数据记为cur,如果cur存在左子树或者右子树,那么把他们存入到数组q里去,一直循环直到q数据没有数据为止。
- 这样不仅可以把数据从上到下,从左到右读一遍,并且可以知道这些数据在那一层,根据题目要求,我们把最后一层的数据相加得到的就是答案了。
- 使用TypeScript根基题目改写代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function deepestLeavesSum(root: TreeNode | null): number {
let sum: number = 0
let q: TreeNode[] = []
let temp: number[] = []
q.push(root)
while (q.length) {
const size: number = q.length
temp = []
for (let i = 0; i < size; i++) {
const cur: TreeNode = q.shift()
temp.push(cur.val)
if (cur.left !== null) q.push(cur.left)
if (cur.right !== null) q.push(cur.right)
}
}
for (let i = 0; i < temp.length; i++) {
sum += temp[i]
}
return sum
};
- 这里我新定义了一个temp,while每次循环都意味着开始遍历下一层。
- 循环过程中是无法确认有没有下一层的,所以每次循环的时候都把temp更新一下。
- 最后得到的temp中的数据就是二叉树中最深层的数据,我们把他们相加得到的就是答案。
思路二:深度优先遍历
- 深度优先遍历有点像回溯,有时候leetcode的每日一题会专门给我们练习回朔+减枝来练习各种题目,有兴趣的小伙伴现在可以去专门找来做做,还是蛮有意思的。
- 先上JavaScript代码看一下大致思路。
// 深度优先(二叉树)
function dfs(node) {
let list = []
help(node, [])
function help(root, temp) {
if (root.left == null && root.right == null) {// 这一步可以换成其他条件
const pushTemp = temp.slice()
pushTemp.push(root.val)
list.push(pushTemp)
return
}
temp.push(root.val)
if (root.left) {
help(root.left, temp)
}
if (root.right) {
help(root.right, temp)
}
temp.pop()
}
}
- 这里的关键在于help函数中的temp参数,每次递归只要他存在子节点就可以一直递归下去,并且每次都会把当前节点的数据push到temp中,一直到叶子结点的时候,会把temp pop一次,删除最后一个数据,之后重新加入其他节点信息。
- list最后得到的数据就是从根节点出发,一直到每个叶子结点的路径。
root.left == null && root.right == null这个条件在会溯的时候可以根据题目换成别的条件,因为在二叉树里是需要找到left和right都为null的叶子结点再返回,所以二叉树里的条件便是这个。- 使用TypeScript根基题目改写代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function deepestLeavesSum(root: TreeNode | null): number {
let list:Array<Array<number>> = [], maxDeep:number = 0, sum: number = 0
help(root, [])
function help(root: TreeNode | null, temp: number[]) {
if (root.left == null && root.right == null) {
const pushTemp = temp.slice()
pushTemp.push(root.val)
list.push(pushTemp)
let deep:number = temp.length
if (deep > maxDeep) {
sum = pushTemp[pushTemp.length - 1]
maxDeep = deep
} else if (deep === maxDeep) {
sum += pushTemp[pushTemp.length - 1]
}
return
}
temp.push(root.val)
if (root.left) {
help(root.left, temp)
}
if (root.right) {
help(root.right, temp)
}
temp.pop()
}
return sum
};
- 根据这个思路,在找到页面结点后,temp的数组长度就是该叶子结点的深度。为了消除堆的问题影响其他递归的时候对temp的影响,我们把temp浅拷贝到pushTemp中。
- pushTemp数组的长度就是该叶子结点的深度,我们找到最深的几个叶子结点并相加,就可以得到答案了。
总结
- 就这道题目来说明显广度优先遍历会更加适合。
- 回溯的思路可以用来解决非常非常多的问题,不仅仅是二叉树,很多题目可以先把他转化为n叉树,然后用回溯的思想来解决。
- 广度优先遍历非常耗空间,很多时候是牺牲空间优化时间的一个解决方案,有时候要慎用,广度优先很容易爆栈,当然在这道题目里没有这个问题。