算法通关村第八关——轻松搞定二叉树的深度和高度问题

1.基础知识

二叉树节点的高度:指从当前节点到叶子节点的最长简单路径边的条数

二叉树节点的深度:指从根节点到当前节点的最长简单路径边的条数

二叉树的深度和高度问题,递归思想的运用很是普遍,有的问题层序遍历也可以解决。

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第1张图片

2.最大深度问题

力扣104题,给定一个二叉树,找出最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明:叶子节点是指没有子节点的节点。

递归的三步走:输入输出参数、递归终止条件、完整的递归逻辑。

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第2张图片

分析:对于rootNode(7),最大深度很明显是左右子节点深度+1,当然,左右子节点存在为空的情况,但只要有一个不为空,树的最大高度就是1+1=2。再增加几个节点:

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第3张图片

对于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为准。

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第4张图片

分析:对于rootNode(7),左右孩子如果只有一个,高度差就是1;如果左右孩子都有或者都没有,则高度差为0。再增加一层。

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第5张图片

对于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。说明:叶子节点是指没有子节点的节点。

算法通关村第八关——轻松搞定二叉树的深度和高度问题_第6张图片

最小深度的一层必须是要到叶子节点

分析终止条件:

  • 如果左子树为空,右子树不为空,说明最小深度是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
}

总结

牢记递归三步走:

  1. 从局部到整体递推
  2. 分情况讨论,明确递归终止条件
  3. 得出完整的递归逻辑

如果想验证的话,就从整体到局部画图推演。

你可能感兴趣的:(算法,算法,数据结构,javascript,前端)