二叉树层序遍历
102.二叉树的层序遍历
题目
思路
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
使用队列实现二叉树广度优先遍历,动画如下:
这样就实现了层序从左到右遍历二叉树。
代码如下(JavaScript)
这份代码可以作为二叉树层序遍历的模板,层序遍历基本上就参照这个代码实现。
var levelOrder = function(root) {
//二叉树的层序遍历
let res=[]
let queue=[]
queue.push(root)
if(!root){
return res
}
while(queue.length){
//记录当前层级节点数
let length=queue.length
//存放每一层的节点
let curLevel=[]
while(length--){
let node =queue.shift()
curLevel.push(node.val)
//存放当前下一层的节点
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
//把每一层的结果放到结果数组
res.push(curLevel)
}
return res
};
107.二叉树的层次遍历 II
题目
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路
相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
代码如下(JavaScript)
var levelOrderBottom = function(root) {
let res=[], queue=[]
queue.push(root)
while(queue.length && root!==null){
//计算当前层级节点数量
let length=queue.length
//存放每一层的节点
let curLevel=[]
while(length--){
let node =queue.shift()
//把当前层节点存入curLevel数组
curLevel.push(node.val)
//把下一层级的左右节点存入queue队列
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
//从数组前头插入值,避免最后反转数组,减少运算时间
res.unshift(curLevel)
}
return res
};
199.二叉树的右视图
题目
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
代码如下(JavaScript)
var rightSideView = function(root) {
//二叉树右视图 只需要把每一层最后一个节点存储到res数组
let res=[],queue=[]
queue.push(root)
while(queue.length && root!==null){
//记录当前层级节点个数
let length=queue.length
while(length--){
let node =queue.shift()
//length长度为0的时候表明到了层级最后一个节点
if(!length){
res.push(node.val)
}
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return res
};
637.二叉树的层平均值
题目
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差
10^-5
以内的答案可以被接受。
思路
本题就是层序遍历的时候把一层求个总和在取一个均值。
代码如下(JavaScript)
var averageOfLevels = function (root) {
//层级平均值
let res = [], queue = []
queue.push(root)
while (queue.length && root !== null) {
//每一层节点个数
let length = queue.length
//sum记录每一层的和
let sum = 0
for (let i = 0; i < length; i++) {
let node = queue.shift()
sum += node.val
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
//每一层的平均值存入数组res
res.push(sum / length)
}
return res
};
429.N叉树的层序遍历
题目
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
思路
这道题依旧是模板题,只不过一个节点有多个孩子了
代码如下(JavaScript)
//每一层可能有2个以上,所以不再使用node.left 和 node.right
var levelOrder = function (root) {
let res = [], queue = []
queue.push(root)
while (queue.length && root !== null) {
//记录每一层节点个数还是和二叉树一致
let length = queue.length
//存放每层节点 也和二叉树一致
let curLevel = []
for (let i = 0; i < length; i++) {
let node = queue.shift()
curLevel.push(node.val)
//这里不再是node.left node.right 而是循环node.children
for(let item of node.children){
item && queue.push(item)
}
}
res.push(curLevel)
}
return res
};
515.在每个树行中找最大值
题目
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
思路
层序遍历,取每一层的最大值
代码如下(JavaScript)
var largestValues = function(root) {
//使用层序遍历
let res = [], queue = [];
queue.push(root);
while(root !== null && queue.length) {
//设置max初始值就是队列的第一个元素
let max = queue[0].val;
let length = queue.length;
while(length--) {
let node = queue.shift();
max = max > node.val ? max : node.val;
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
//把每一层的最大值放到res数组
res.push(max);
}
return res;
};
116.填充每个节点的下一个右侧节点指针
题目
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
思路
本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
代码如下(JavaScript)
var connect = function (root) {
let queue = []
queue.push(root)
while (queue.length && root !== null) {
let len = queue.length
for (let i = 0; i < len; i++) {
let node = queue.shift()
if (i < len - 1) {
node.next = queue[0]
}
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return root
};
117.填充每个节点的下一个右侧节点指针II
题目
给定一个二叉树:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
思路
这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道
代码如下(JavaScript)
var connect = function(root) {
let queue=[]
queue.push(root)
while(queue.length && root!==null){
let len =queue.length
for(let i=0;i<len;i++){
let node =queue.shift()
if(i<len-1){
node.next=queue[0]
}
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return root
};
104.二叉树的最大深度
题目
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
思路
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
代码如下(JavaScript)
// 最大的深度就是二叉树的层数
var maxDepth = function (root) {
let queue = []
queue.push(root)
let height = 0
while (queue.length && root != null) {
let len = queue.length
height++
for (let i = 0; i < len; i++) {
let node = queue.shift()
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return height
};
111.二叉树的最小深度
题目
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
思路
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
代码如下(JavaScript)
var minDepth = function(root) {
if(root===null) return null
let queue=[root]
let depth=0
while(queue.length){
let len=queue.length
depth++
for(let i=0;i<len;i++){
let node =queue.shift()
// 如果左右节点都是null,则该节点深度最小
if(node.left===null && node.right===null){
return depth
}
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return depth
};
总结
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时又发现队列的一个应用了)。
226.翻转二叉树
题目
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
思路
仔细想想,可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
关键在于遍历顺序,前中后序应该选哪一种遍历顺序?
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
这里使用递归法,我们下文以前序遍历为例,通过动画演示一下:
递归的基本思路可以参考我上一篇文章
代码如下(JavaScript)
递归遍历
//递归遍历--后序遍历
var invertTree = function(root) {
if(!root) return root
let left = invertTree(root.left)
let right = invertTree(root.right)
root.left = right
root.right = left
return root
};
迭代遍历
//迭代遍历--前序遍历
//节点交换函数
const invertNode = function (node, left, right) {
let temp = left
left = right
right = temp
node.left = left
node.right = right
}
var invertTree = function (root) {
if(!root) return root
const stack = [root]
while(stack.length){
let node = stack.pop()
invertNode(node,node.left,node.right)
node.right && stack.push(node.right)
node.left && stack.push(node.left)
}
return root
};
层序遍历
//层序遍历
//节点交换函数
const invertNode = function(node,left,right){
let temp = left
left = right
right =temp
node.left = left
node.right = right
}
var invertTree = function (root) {
const queue = []
if (root) queue.push(root)
while (queue.length) {
let size = queue.length
while (size--) {
let node = queue.shift()
invertNode(node, node.left, node.right)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return root
};
101. 对称二叉树
题目
给你一个二叉树的根节点 root
, 检查它是否轴对称。
思路
首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
那么如何比较呢?
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
那么遍历的顺序应该是什么样的呢?
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
代码如下(JavaScript)
递归法
var isSymmetric = function (root) {
if (root === null) return true
//使用递归遍历左右子树 递归三部曲
//1.确定递归的参数 root.left root.right 和返回值true false
const compareNode = function (left, right) {
// 2. 确定终止条件 空的情况
if (left !== null && right === null || left === null && right !== null) return false
else if (left === null && right === null) return true
else if (left.val !== right.val) return false
//3.确定单层递归逻辑
let outSide = compareNode(left.left, right.right)
let intSide = compareNode(left.right, right.left)
return outSide && intSide
}
return compareNode(root.left, root.right)
};
迭代法
//迭代遍历
var isSymmetric = function (root) {
const stack = []
if (!root) return true
stack.push(root.right) //将右子树头节点加入栈中
stack.push(root.left) //将左子树头节点加入栈中
//接下来就是要判断这两个树是否相互翻转
while (stack.length) {
let left = stack.pop()
let right = stack.pop()
//左右节点有一个不为空,返回false
if(!left && right || left && !right) return false
//左右节点都为空,此时说明是对称的
else if(!left && !right) continue
//左右节点都不为空但数值不相同, 返回false
else if(left.val !==right.val) return false
stack.push(right.right) //加入右节点右孩子
stack.push(left.left) //加入左节点左孩子
stack.push(right.left) //加入右节点左孩子
stack.push(left.right) //加入左节点右孩子
}
return true
};