背景
写这篇文章主要是记录一下自己面试被问到的这个问题: 求二叉树每层最大值 到 构造一颗二叉树去验证 到进一步优化循环层数 的解答过程。(面试常见套路,手写->优化/其他解法/时间空间复杂度分析)
时间一久就会忘记怎么写,下次又得想半天,而面试肯定不会让现场想半天的,所以需要熟记思路。
这个题目源于我写的字节跳动实习的面经 https://segmentfault.com/a/1190000038543869,将解答过程写进面经里面会比较长,所以单独抽出来总结一下。
1. 求二叉树每层的最大节点
这个方法比较常见,记得数据结构书中二叉树层序遍历的代码示例就是这个方法,leetcode的题解也多是两层循环。
ps:如果一开始就写出一层循环那当然不会有后续让优化的问题了。但推荐写这种,后续大多会被问到怎么用一层实现(我小米和字节都被问到了)。这时再装作想一想然后写出来岂不是更显得自己应变能力强,有算法基础而不是背出来的?
var largestValues = function(root) {
if(!root) return []
const res = [] // 最终输出的结果,存放每层的最大值
let queue = [root] // 根节点入队
while(queue.length) {
let len = queue.length
let tmp = [] // 存放当前层的所有节点的值
while(len) { // 此处的len记录的是当前层的节点数,为0代表遍历完当前层的节点,开始遍历下一层
let curr = queue.shift()
if(curr.left) queue.push(curr.left)
if(curr.right) queue.push(curr.right)
tmp.push(curr.val)
len--
}
res.push(Math.max(...tmp))
}
return res
}
2. 构造一颗二叉树,用于验证我们写的求每层最大值的函数
// 二叉树的节点的构造方法
function TreeNode(val, left, right) {
this.val = val
this.left = null
this.right = null
}
// 接下来我们构建一颗第一层为1,第二层为2,3,第三层为4,5,6的二叉树
let p1 = new TreeNode(1)
let p2 = new TreeNode(2)
let p3 = new TreeNode(3)
let p4 = new TreeNode(4)
let p5 = new TreeNode(5)
let p6 = new TreeNode(6)
// 将这些节点相互连接起来
p1.left = p2
p1.right = p3
p2.left = p4
p2.right = p5
p3.left = p6
// 验证
console.log(largestValues(p1)); // 输出 [1, 3, 6]
3. 循环层数优化:将刚刚写的求二叉树每层最大值的方法中的两层循环改为一层
思路:在前面的方法中,我们内层循环的作用是记录当前层的节点数,让我们知道什么时候本层节点遍历完。既然少了一层循环,那我们就只能用额外空间去记录下一层节点了
var largestValuesImprove = function(root) {
if(!root) return []
const res = [] // 最终输出的结果,存放每层的最大值
let queue = [root] // 存放当前层的节点
let nextLevel = [] // 存放下一层的节点
let tmp = []
while(queue.length>0 || nextLevel.length>0) {
if(queue.length===0) { // 当前层遍历完
queue = nextLevel // 取下一层
nextLevel = []
res.push(Math.max(...tmp))
tmp = []
}
let curr = queue.shift()
tmp.push(curr.val)
if(curr.left) nextLevel.push(curr.left)
if(curr.right) nextLevel.push(curr.right)
}
// 这里一定要注意最后一次跳出循环时没有走if语句,需要再把最后一层的值放入res
res.push(Math.max(...tmp))
return res
}
// 验证
console.log(largestValuesImprove(p1));
完整代码
// 1. 求二叉树每层的最大节点
var largestValues = function(root) {
if(!root) return []
const res = [] // 最终输出的结果,存放每层的最大值
let queue = [root] // 根节点入队
while(queue.length) {
let len = queue.length
let tmp = [] // 存放当前层的所有节点的值
while(len) { // 此处的len记录的是当前层的节点数,为0代表遍历完当前层的节点,开始遍历下一层
let curr = queue.shift()
if(curr.left) queue.push(curr.left)
if(curr.right) queue.push(curr.right)
tmp.push(curr.val)
len--
}
res.push(Math.max(...tmp))
}
return res
}
// 2. 构造一颗二叉树,用于验证我们写的求每层最大值的函数
function TreeNode(val, left, right) { // 节点的构造方法
this.val = val
this.left = null
this.right = null
}
// 接下来我们构建一颗第一层为1,第二层为2,3,第三层为4,5,6的二叉树
let p1 = new TreeNode(1)
let p2 = new TreeNode(2)
let p3 = new TreeNode(3)
let p4 = new TreeNode(4)
let p5 = new TreeNode(5)
let p6 = new TreeNode(6)
// 将这些节点相互连接起来
p1.left = p2
p1.right = p3
p2.left = p4
p2.right = p5
p3.left = p6
// 3. 验证
console.log(largestValues(p1)); // 输出 [1, 3, 6]
// 4. 循环层数优化:将我们刚刚写求二叉树每层的最大值中两层循环改为一层
// 思路:在前面的方法中,我们内层循环的作用是记录当前层的节点数,让我们知道什么时候本层节点遍历完。
// 既然少了一层循环,那我们就只能用额外空间去记录下一层节点了
var largestValuesImprove = function(root) {
if(!root) return []
const res = [] // 最终输出的结果,存放每层的最大值
let queue = [root] // 存放当前层的节点
let nextLevel = [] // 存放下一层的节点
let tmp = []
while(queue.length>0 || nextLevel.length>0) {
if(queue.length===0) { // 当前层遍历完
queue = nextLevel // 取下一层
nextLevel = []
res.push(Math.max(...tmp))
tmp = []
}
let curr = queue.shift()
tmp.push(curr.val)
if(curr.left) nextLevel.push(curr.left)
if(curr.right) nextLevel.push(curr.right)
}
// 这里一定要注意最后一次跳出循环时没有走if语句,需要再把最后一层的值放入res
res.push(Math.max(...tmp))
return res
}
console.log(largestValuesImprove(p1));
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。