二叉树的深度算法,是二叉树中比较基础的算法了。对应 LeetCode 第104题。html
而后你会发现 LeetCode 后面有些算法题须要用到这个算法的变形,好比第110题、543题。这两道题,若是你知道二叉树深度算法的递归过程,就很容易作出来。node
关于二叉树的相关知识,能够看个人这篇文章:数据结构】树的简单分析总结(附js实现)算法
给定一个二叉树,找出其最大深度。编程
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。数组
说明: 叶子节点是指没有子节点的节点。缓存
示例: 给定二叉树 [3,9,20,null,null,15,7],bash
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
复制代码
注意这里的二叉树是经过 链式存储法 存储的,而不是数组。网络
在解题以前,咱们先了解下什么是递归(若是你已经掌握,请直接跳过这节)。数据结构
那么就开始朗(wang)诵(ba)课本(nian)内容(jing)。函数
递归分为 “递” 和 “归”。“递” 就是传进去,“归”就是一个函数执行完解决了一个子问题。递归的实现经过不停地将问题分解为子问题,并经过解决子问题,最终解决原问题。
递归的核心在于递归公式,当咱们分析出递归公式后,递归问题其实也就解决了。递归是一种应用普遍的编程技巧,不少地方都要用到它,好比深度优先遍历(本题就用到这个)、二叉树的前中后序遍历。
递归须要知足三个条件:
递归的特色是代码比较简洁,虽然大多数状况下你都比较难理解递归的每一个过程,由于它不符合人类的思惟习惯,但其实你也没必要去真正了解,你只要知道B和 C 被解决后,能够推导出 A 就行,无需考虑 B 和 C 是如何经过子问题解决的(由于都和前面同样的!)。
其次递归若是太深,可能会致使内存用尽。由于递归的时候要保存许多调用记录,就会维护一个调用栈,当栈太大而超过了可用内存空间,就会发生内存溢出的状况,咱们称之为 堆栈溢出。解决方案有下面 4 种:
说到递归,那就不得不提递归的一道经典题目了,那就是“爬楼梯问题”,对应 LeetCode 第70题。
爬楼梯的问题描述是:假设你正在爬楼梯。须要 n (正整数)阶你才能到达楼顶。每次你能够爬 1 或 2 个台阶。你有多少种不一样的方法能够爬到楼顶呢?
首先你能够列出 n = 1,n = 2... 的走法,试着找出规律。
阶 | 走法 | 走法总数 |
---|---|---|
1 | 1 | 1 |
2 | 1 + 1,2 | 2 |
3 | 1 + 2, 1 + 1 + 1, 2 + 1 | 3 |
到这里咱们就能够发现一些规律了。那就是 走到第 3 阶的走法为 2 阶 和 1 阶的和。为何会这样呢?咱们就要透过现象发现本质,本质就是,要走到第 n 阶,首先就要先走到 第 n-1 阶,而后再爬一个台阶,或者是先走到 n - 2 阶,而后爬两个台阶。
因此咱们获得这么一个递归公式:f(n) = f(n-1) + f(n - 2)
递归写法:
var climbStairs = function(n) {
let map = {};
function f(n) {
if (n < 3) return n;
if (map[n]) return map[n];
let r = f(n-1) + f(n - 2);
map[n] = r;
return r;
}
return f(n)
复制代码
由于 f(n) = f(n-1) + f(n-2)。这里的f(n-1),又由 f(n-2)+f(n-3) 得出。这里的 f(n-2) 被执行了两次,因此就须要缓存 f(n-2) 的结果到 map 对象中,来减小运算时间。
循环写法:
var climbStairs = function(n) {
if (n < 3) return n;
let step1 = 1, // 上上一步
step2 = 2; // 上一步
let tmp;
for (let i = 3; i <= n; i++) {
tmp = step2;
step2 = step1 + step2;
step1 = tmp;
}
return step2;
};
复制代码
说完递归后,咱们就来分析题目吧。
首先咱们试着找出递归规律。首先咱们知道,除了叶子节点,二叉树的全部节点都有会有左右子树。那么若是咱们知道左右子树的深度,找出两者之间的最大值,而后再加一,不就是这个二叉树的深度吗?其次以 叶子节点 为根节点的二叉树的高度是 1,咱们就能够根据经过这个做为递归的结束条件。
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */
/** * @param {TreeNode} root * @return {number} */
var maxDepth = function(root) {
function f(node) {
if (!node) return 0;
return Math.max(f(node.left), f(node.right)) + 1;
}
return f(root);
};
复制代码
这里用到了深度优先遍历,会沿着二叉树从根节点往叶子节点走。另外,由于没有重复计算,因此不须要对结果进行缓存。还有就是,由于没有多余的变量要保存,能够直接把 maxDepth 函数写成递归函数。
关于如何用数组存储(顺序存储法)的二叉树,这里就不提了,请看我前面提到的相关文章。
求一个数组表示的二叉树的深度,能够看做求 对应的彻底二叉树的深度。
在此以前,咱们先看看如何求出一个节点个数为 n 的 满二叉树 的深度 k。
深度 k | 个数 n |
---|---|
1 | 1 |
2 | 3 (=1+2) |
3 | 7 (=1+2+4) |
4 | 15 (=1+2+4+8) |
规律很明显,经过等比数列求和公式化简,咱们获得 k = Math.log2(n+1)
,其中 k 为深度,n 为满二叉树的节点个数。那么对于一个彻底二叉树来讲,将 k 向上取整便可:k = Math.ceil( Math.log2(n+1) )
。
因此对于一个顺序存储法存储的长度为 n 的二叉树,其高度 k 为:
k = Math.ceil( Math.log2(n+1) )
复制代码
(须要注意的是,这里的数组是从 0 开始存储节点的。)