系列文章導圖:
檢視所有系列文章: xiaozhuanlan.com/leetcode_tc
1. 知識點
本章節重點介紹二叉樹的廣度和深度優先搜尋。
1.1 二叉樹的遍歷
前序遍歷、中序遍歷、後序遍歷在之前將二叉樹的時候已經提到過,這裡不再重複,需要再次瞭解的請看下面連結。 二叉樹的遍歷
1.2 BFS 廣度優先搜尋
下面介紹一下BFS,即廣度優先搜尋,下面圖解示例看BFS是如何遍歷的:
上面四幅圖,1-2-3-4,比較清晰的描述了BFS的是如何遍歷的,可以理解為是按層進行掃蕩。
1.3 DFS 深度優先搜尋
跟BFS一樣,同樣給出圖解示例,帶大家理解DFS的遍歷過程:
可以看出DFS,是一般先遍歷左子樹,並且將左子樹的子孫全部遍歷完,直到葉子節點,然後回到根節點,繼續遍歷右子樹。
1.4 BFS vs DFS
根據上面對BFS和DFS的介紹,給出一張直觀圖來對比二者的區別:
2. 題目
2.1 108. Convert Sorted Array to Binary Search Tree
-
題目要求 將一個按照升序排列的有序陣列,轉換為一棵高度平衡二叉搜尋樹。
-
Example
Given the sorted array: [-10,-3,0,5,9],
One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:
0
/ \
-3 9
/ /
-10 5
複製程式碼
-
解題思路 這題目主要考察對二叉搜尋樹的瞭解以及中序遍歷的知識點。 容易想到和理解的一條思路,就是利用二叉搜尋樹按中序遍歷是有序的特性。而中序遍歷的規則是 左-根-右。即陣列的中間值則是根節點,左右兩邊則分別是左子樹和右子樹,按遞迴的思維,左子樹和右子樹也按照上面的邏輯進行樹的轉換。
-
程式碼演示
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""
if len(nums) == 0:
return None
mid = int(len(nums) / 2)
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums[:mid])
root.right = self.sortedArrayToBST(nums[mid + 1:])
return root
複製程式碼
2.2 111. Minimum Depth of Binary Tree
-
題目要求 Given a binary tree, find its minimum depth.找出二叉樹的最小深度。
-
Example
Given binary tree [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 return its minimum depth = 2. 複製程式碼
2.2.1 解題思路一: 遞迴
-
思路 上一篇講了二叉樹的最高的深度,用的就是遞迴思維,所以找出二叉樹的最小深度,我們先來用遞迴解題,思路就是遞迴查詢左子樹和右子樹的深度,然後取二者的小者即可。
-
程式碼演示
class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
if root.left is None:
return self.minDepth(root.right) + 1
if root.right is None:
return self.minDepth(root.left) + 1
left = self.minDepth(root.right) + 1
right = self.minDepth(root.left) + 1
return min(left, right)
複製程式碼
程式碼也比較清晰,可以跟找出二叉樹的最大深度進行對比。也就是分別找出左子樹和右子樹的深度,取小者返回即可。
時間複雜度O(N)
2.2.2 解題思路二: DFS
-
思路 思路二則採用今天的主題,DFS來解,按照知識點的描述,DFS是先將左子樹的自身遍歷完畢,再回來遍歷右子樹。DFS非遞迴解法一般需要採用棧Stack資料結構,按層將右子樹和左子樹依次入棧,下一次先出左子樹(後入),然後進行下一輪,判斷左子樹的子孫,直到左子樹遍歷完畢,獲取到左子樹的最高深度;右子樹也是如此。如果該節點是葉子節點,則說明已經到底,將此深度與最小深度進行比較。
-
程式碼演示
class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
DFS O(n)
"""
if not root:
return 0
stack = [(root, 1)]
min_depth = float("inf")
while stack:
cur_node, cur_depth = stack.pop()
children = [cur_node.right, cur_node.left]
if not any(children):
min_depth = min(min_depth, cur_depth)
for child in children:
if not child:
continue
stack.append((child, cur_depth + 1))
return min_depth
複製程式碼
先定義一個min_depth,初始化為正無窮,python里正無窮的表達方式是float("inf")。然後檢查該節點是否是葉子節點,any([])是表示該陣列中是否全為空,如果是則返回True,否則返回False。如果左節點和右節點全為空,則表示是葉子節點,則取該深度與最小深度的小者。然後按先右後左的順序依次入棧即可。
時間複雜度O(N)
2.2.3 解題思路三: BFS
-
思路 思路三採用BFS,逐層掃蕩二叉樹,BFS在此題中,還有一點優勢是如果發現某一層出現葉子節點,則直接返回此節點所處的深度就是答案,因為逐層掃蕩下去,最先出現葉子節點的層肯定也是深度最小,肯定越往下深度越高。BFS非遞迴解法則需要藉助雙端佇列的資料結構,因為需要從首邊出,尾部進,滿足先進先出的特點。
-
程式碼演示
from collections import deque
class Solution3(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
BFS O(n)
"""
if not root:
return 0
queue = deque([(root, 1)])
while queue:
cur_node, cur_depth = queue.popleft()
children = [cur_node.left, cur_node.right]
if not any(children):
return cur_depth
for child in children:
if not child:
continue
queue.append((child, cur_depth + 1))
複製程式碼
deque則是Python中雙端佇列的實現。也是先檢查該節點是否是葉子節點,如果是則直接返回結果,否則進行將下一層的左右節點分別入佇列
時間複雜度 O(n)
2.2.4 思考題
嘗試將上一篇中求二叉樹的最高深度,leetcode第104題,也按照DFS和BFS的思路進行求解。如果能自由的寫出來,說明你已經掌握DFS和BFS了。
2.3 102. Binary Tree Level Order Traversal
-
題目要求 Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level). 給定一顆二叉樹,按層進行遍歷,要求先左後右.
-
Example
Given binary tree [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 return its level order traversal as: [ [3], [9,20], [15,7] ] 複製程式碼
2.3.1 解題思路一: BFS
-
思路 按層進行遍歷,不就是BFS的特性嘛,所以最直接解法就是BFS了,上面題目已經講解了BFS是如何實現,接下來看程式碼講解.
-
程式碼演示
from collections import deque
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
BFS
"""
if not root:
return []
queue = deque([root])
result = []
while queue:
cur_level = []
for _ in range(len(queue)):
cur_node = queue.popleft()
cur_level.append(cur_node.val)
if cur_node.left:
queue.append(cur_node.left)
if cur_node.right:
queue.append(cur_node.right)
result.append(cur_level)
return result
複製程式碼
BFS非遞迴實現,首先初始化雙端佇列,然後判斷一層的節點個數,迴圈每一個節點,進行遍歷,同時將每一個節點的孩子進入佇列,進行下一輪遍歷。
2.3.2 解題思路二: DFS非遞迴實現
-
思路 如果此題按DFS是否可以實現了?答案肯定是可以的,麻煩在於需要記住每一層的level,這樣遍歷左子樹後,遍歷右子樹時需要找到所屬的層數。即需要知道每一個節點所屬哪一層。
-
程式碼實現
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
DFS
"""
if not root:
return []
stack, result = [], []
stack.append((root, 0))
while stack:
cur_node, level = stack.pop()
if len(result) < level + 1:
result.append([])
result[level].append(cur_node.val)
children = [cur_node.right, cur_node.left]
if not any(children):
continue
for child in children:
if not child:
continue
stack.append((child, level + 1))
return result
複製程式碼
DFS非遞迴實現採用棧,棧中的元素是一個元組,第一個元素是節點,第二個元素是元素的層數,這樣遍歷到該節點時,可以將該節點的值放入所屬的層次的陣列中。 需要注意的是孩子們的壓棧順序是先右孩子再左孩子,因為需要先遍歷左孩子,後進先出。
2.3.3 解題思路三: DFS遞迴實現
DFS這裡演示如何使用遞迴實現,思路跟解題思路二一致。
- 程式碼演示
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
DFS
"""
if not root:
return []
self.result = []
self.dfs(root, 0)
return self.result
def dfs(self, node, level):
if not node:
return
if len(self.result) < level + 1:
self.result.append([])
self.result[level].append(node.val)
self.dfs(node.left, level + 1)
self.dfs(node.right, level + 1)
複製程式碼
這裡需要藉助一個函式dfs,傳入2個引數,一個是節點,一個是節點所屬的層數,這樣可以知道該節點的值該加入哪一層。
2.3.4 思考題
leetcode 第107題,是該題的變種:107. Binary Tree Level Order Traversal II
Example:
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its bottom-up level order traversal as:
[
[15,7],
[9,20],
[3]
]
複製程式碼
從底部按層遍歷,解題思路也是DFS和BFS,歡迎大家在評論區交流。
可以看出DFS和BFS的解題思路是可以複製的,基本都是這個套路,如果你以完全理解,則相關題目是完全難不倒你。
檢視所有系列文章: xiaozhuanlan.com/leetcode_tc
更多文章請關注公眾號 『天澄技術雜談』