二叉樹的深度算法,是二叉樹中比較基礎的算法了。對應 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 開始存儲節點的。)