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,返回-1if (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.最后一种情况,也就是左右孩子都不为空,返回左右子树最小深度+1return 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
}
总结
牢记递归三步走:
- 从局部到整体递推
- 分情况讨论,明确递归终止条件
- 得出完整的递归逻辑
如果想验证的话,就从整体到局部画图推演。