題目介紹
描述:
給定一個根爲 root 的二叉樹,每個結點的深度是它到根的最短距離。
如果一個結點在整個樹的任意結點之間具有最大的深度,則該結點是最深的。
一個結點的子樹是該結點加上它的所有後代的集合。
返回能滿足“以該結點爲根的子樹中包含所有最深的結點”這一條件的具有最大深度的結點。
解題思路:
遞歸算法的關鍵是要明確函數的「定義」是什麼,然後相信這個定義,利用這個定義推導最終結果。
寫樹相關的算法,簡單說就是,先搞清楚當前 root 節點該做什麼,然後根據函數定義遞歸調用子節點,遞歸調用會讓孩子節點做相同的事情。
二叉樹題目的一個難點在於如何通過題目的要求思考出每一個節點需要做什麼
二叉樹解題策略
一 遞歸 二 隊列 + 迭代 (層次遍歷) 三 棧 + 迭代 (非遞歸遍歷) 四 其它
三種基本的遍歷方式,都可以用遞歸來實現。寫遞歸算法的時候,需要注意遞歸退出條件以及遞歸操作的表達。
自己的解法實現
def subtreeWithAllDeepest4(self, root):
def dfs(node):
if not node:
return None, 0
left, i = dfs(node.left)
right, j = dfs(node.right)
if i > j:
return left, i + 1
elif j > i:
return right, j + 1
return node, i + 1
return dfs(root)[0]
網上比較優秀的解法
解法一
方法一: 兩次深度優先搜索 思路 最直白的做法,先做一次深度優先搜索標記所有節點的深度來找到最深的節點,再做一次深度優先搜索用回溯法找最小子樹。定義第二次深度優先搜索方法爲 answer(node),每次遞歸有以下四種情況需要處理:
- 如果 node 沒有左右子樹,返回 node。
- 如果 node 左右子樹的後代中都有最深節點,返回 node。
- 如果只有左子樹或右子樹中有且擁有所有的最深節點,返回這棵子樹的根節點(即 node 的左/右孩子)。
- 否則,當前子樹中不存在答案。
算法 先做一次深度優先搜索標記所有節點的深度,再做一次深度優先搜索找到最終答案。
def subtreeWithAllDeepest(self, root):
# Tag each node with it's depth.
depth = {None: -1}
def dfs(node, parent = None):
if node:
depth[node] = depth[parent] + 1
dfs(node.left, node)
dfs(node.right, node)
dfs(root)
max_depth = max(depth.itervalues())
def answer(node):
# Return the answer for the subtree at node.
if not node or depth.get(node, None) == max_depth:
return node
L, R = answer(node.left), answer(node.right)
return node if L and R else L or R
return answer(root)
解法二
方法二: 一次深度優先搜索 思路 可以把 方法一 中兩次深度優先搜索合併成一次,定義方法 dfs(node),與方法一中不同的是 dfs(node) 返回兩個值,子樹中的答案和 node 節點到最深節點的距離。
算法 dfs(node) 返回的結果有兩個部分:
- Result.node:包含所有最深節點的最小子樹的根節點。
- Result.dist:node 到最深節點的距離。 分別計算 dfs(node) 的兩個返回結果:
對於 Result.node: 如果只有一個 childResult 具有最深節點,返回 childResult.node。
如果兩個孩子都有最深節點,返回 node。
Result.dist 爲 childResult.dist 加 1。
def subtreeWithAllDeepest2(self, root):
import collections
Result = collections.namedtuple("Result", ("node", "dist"))
def dfs(node):
if not node: return Result(None, 0)
L, R = dfs(node.left), dfs(node.right)
if L.dist > R.dist:
return Result(L.node, L.dist + 1)
if L.dist < R.dist:
return Result(R.node, R.dist + 1)
return Result(node, L.dist + 1)
return dfs(root).node
解法三
利用遞歸的思想。首先,要明確只有當左子樹和右子樹的深度相同時,我們才返回以當前結點爲根的子樹!,否則遞歸深度更深的左右子樹。
def depth(self, tree): # 計算樹的深度
if tree == None:
return 1
return max(self.depth(tree.left), self.depth(tree.right)) + 1
def subtreeWithAllDeepest3(self, root):
left_depth = self.depth(root.left)
rigth_depth = self.depth(root.right)
if left_depth == rigth_depth:
return root
else:
return self.subtreeWithAllDeepest(root.left) if left_depth > rigth_depth else self.subtreeWithAllDeepest(root.right)
相關知識總結和思考
相關知識:
BFS:廣度/寬度優先。其實就是從上到下,先把每一層遍歷完之後再遍歷一下一層。
可以使用Queue的數據結構。我們將root節點初始化進隊列,通過消耗尾部,插入頭部的方式來完成BFS。
二叉搜索樹(BST)的特性:
- 若它的左子樹不爲空,則所有左子樹上的值均小於其根節點的值
- 若它的右子樹不爲空,則所有右子樹上的值均大於其根節點的值
- 它的左右子樹也分別爲二叉搜索樹
遞歸與迭代的區別
遞歸:重複調用函數自身實現循環稱爲遞歸; 迭代:利用變量的原值推出新值稱爲迭代,或者說迭代是函數內某段代碼實現循環;