1.基础知识
二叉树节点的高度:指从当前节点到叶子节点的最长简单路径边的条数
二叉树节点的深度:指从根节点到当前节点的最长简单路径边的条数
二叉树的深度和高度问题,递归思想的运用很是普遍,有的问题层序遍历也可以解决。
2.最大深度问题
力扣104题,给定一个二叉树,找出最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明:叶子节点是指没有子节点的节点。
递归的三步走:输入输出参数、递归终止条件、完整的递归逻辑。
分析:对于rootNode(7)
,最大深度很明显是左右子节点深度+1,当然,左右子节点存在为空的情况,但只要有一个不为空,树的最大高度就是1+1=2
。再增加几个节点:
对于node(3)
,最大深度仍然是左右子节点深度+1,左右子节点只要有一个不为空,node(3)
的最大高度是1+1=2
。而对于rootNode(7)
,则是左右子树中深度最大的一个+1,于是对于rootNode(7)
的判断逻辑用代码表示就是:
let leftDepth = getDepth(root.left);
let rightDepth = getDepth(root.right);
let maxDepth = Math.max(leftDepth, rightDepth) + 1;
递归终止的条件就是root === null
然后返回0
就行。输入参数就是子树的根节点root
,返回值为当前root
所在子树的最大高度。
function maxDepth(root) {
if (root === null) {
return 0;
}
// 得到根节点左右子树的最大深度
let leftDepth = maxDepth(root.left);
let rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
仔细观察可以发现,先拿到左右子树的结果,再计算根节点的深度,这与后序遍历本质一样。
层序遍历也可以得到最大深度,方法就是每遍历一层,设置的记录层数的变量+1即可。代码如下:
function maxDepth(root) {
if (root === null) {
return 0;
}
let maxDepth = 0;
const treeQueue = [root];
while (treeQueue.length !== 0) {
// lenOfTreeQueue表示每一层的元素数
lenOfTreeQueue = treeQueue.length;
// lenOfTreeQueue=0表示一层访问完了
while (lenOfTreeQueue > 0) {
let treeNode = treeQueue.pop();
if (treeNode.left !== null) {
treeQueue.push(treeNode.left);
}
if (treeNode.right !== null) {
treeQueue.push(treeNode.left);
}
lenOfTreeQueue--;
}
maxDepth++;
}
return maxDepth;
}
3 判断平衡二叉树
力扣110题,给定一个二叉树,判断它是否是高度平衡的二叉树。本题中高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。
关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以根节点深度是1,我们以leetcode为准。
分析:对于rootNode(7)
,左右孩子如果只有一个,高度差就是1;如果左右孩子都有或者都没有,则高度差为0。再增加一层。
对于rootNode(7)
,需要知道它的左右子树的最大高度差是否 < 2
。
- 当
root
节点的左右子树高度差< 2
,就返回左右子树最大高度+1 - 当
root
节点左右子树的高度差>= 2
,就返回-1
,表示此子树不是平衡树。
用代码来表示就是:
let leftHeight = getHeight(root.left);
let rightHeight = getHeight(root.right);
return Math.abs(leftHeight - rightHeight) < 2 ? Math.max(leftHeight, rightHeight) + 1 : -1;
完整代码如下:
function isBalanced(root) {
if (nodeHeight(root) >= 0) {
return true;
} else return false;
function nodeHeight(root) {
if (root === null) {
return false;
}
let leftNodeHeight = nodeHeight(root.left);
let rightNodeHeight = nodeHeight(root.right);
// 若当前节点左右子树的高度差<2,返回当前节点的左右子树最大高度+1
// 若当前节点左右子树的高度差>=2,返回-1
if (leftNodeHeight === -1 ||
rightNodeHeight === -1 ||
Math.abs(leftNodeHeight - rightNodeHeight) >= 2) {
return -1;
} else {
return Math.max(leftNodeHeight, rightNodeHeight) + 1;
}
}
}
4.最小深度问题
力扣111题,给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量,例如下面的例子返回结果为2。说明:叶子节点是指没有子节点的节点。
最小深度的一层必须是要到叶子节点。
分析终止条件:
- 如果左子树为空,右子树不为空,说明最小深度是1+右子树的深度
- 相反,如果右子树为空,左子树不为空,最小深度就是1+左子树的深度
- 如果左右子树都不为空,返回左右子树深度中最小值+1.
代码如下:
function minDepth(root) {
if (root === null) {
return 0;
}
// 终止条件有三种
// 1.当前节点的左右子节点都为空,说明到达了叶子节点,leftDepth和rightDepth为0
// 2.左右子树其中一个为空,leftDepth和rightDepth其中一个为0,就返回比较大的那个子树深度
let leftDepth = minDepth(root.left);
let rightDepth = minDepth(root.right);
if {root.left === null || root.right === null} {
return leftDepth + rightDepth + 1;
}
// 3.最后一种情况,也就是左右孩子都不为空,返回左右子树最小深度+1
return Math.min(leftDepth, rightDepth) + 1;
}
这道题同样也能用层序遍历来解决。遍历时,第一次遇到叶子就直接返回其所在层次即可:
function minDepth(root) {
if (root === null) {
return 0;
}
let minDepth = 0;
const treeQueue = [root];
while (treeQueue.length !== 0) {
lenOfTreeQueue = treeQueue.length;
minDepth++;
let treeNode = treeQueue.pop();
// 如果左右子节点均为空,说明是叶子节点,直接返回
if (treeNode.left === null && treeNode.right === null) {
return minDepth;
}
if (treeNode.left !== null) {
treeQueue.push(treeNode.left);
}
if (treeNode.right !== null) {
treeQueue.push(treeNode.left);
}
}
5.N叉树的最大深度
力扣559题,给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔,比如:
示例1:
输入:root = [1,null,3,2,4,null,5,6]
输出:3
示例2:
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:5
分析:N叉树与二叉树不同之处就在于N叉树节点比较多,root
的子节点为N
个,也就是N
个子树。记N
个子树的最大深度中的最大值为maxChildDepth
,那么该N叉树的最大深度为maxChildDepth + 1
。
var maxDepth = function(root) {
if (root === null) {
return 0;
} else if (root.children === null) {
return 1;
}
let maxChildDepth = 0;
const children = root.children;
for(const child of children) {
// 记录当前子树的最大深度
const childDepth = maxDepth(child);
// 将当前子树的最大深度与记录的最大子树深度进行比较
maxChildDepth = Math.max(maxChildDepth, childDepth);
}
return maxChildDepth + 1;
}
用层次遍历来解决的话,我们每次从队列里拿出当前层的所有节点,这样下一次遍历的就是当前层的所有子节点,以此类推。设置一个记录遍历层数的变量。
function maxDepth(root) {
if (root === null) {
return 0;
} else if (root.children === null) {
return 1;
}
const treeQueue = [root];
let maxDepth = 0;
while (treeQueue.length !== 0) {
// 记录每层所有节点数
let lenOfTreeQueue = treeQueue.length;
while (lenOfTreeQueue > 0) {
const node = treeQueue.shift();
const children = node.children;
for (const child of children) {
treeQueue.push(child);
}
lenOfTreeQueue--;
}
maxDepth++;
}
return maxDepth
}
总结
牢记递归三步走:
- 从局部到整体递推
- 分情况讨论,明确递归终止条件
- 得出完整的递归逻辑
如果想验证的话,就从整体到局部画图推演。