注:题号对应leetcode题号,可以在官网搜索题号查询
目录
37.序列化二叉树&297. 二叉树的序列化与反序列化 +1
32.III 从上到下打印二叉树 III- flag insert & 103. 二叉树的锯齿形层次遍历
树大多采用递归
树的定义
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
层次遍历
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
递归
一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
前中后序遍历
1
/ \
2 3
/ \ \
4 5 6
层次遍历顺序:[1 2 3 4 5 6] 前序遍历顺序:[1 2 4 5 3 6] 中序遍历顺序:[4 2 5 1 3 6] 后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
① 前序
void dfs(TreeNode root){
visit(root);
dfs(root.left);
dfs(root.right);
}
② 中序
void dfs(TreeNode root){
dfs(root.left);
visit(root);
dfs(root.right);
}
③ 后序
void dfs(TreeNode root){
dfs(root.left);
dfs(root.right);
visit(root);
}
BST
主要利用 BST 中序遍历有序的特点。
深度有关
104. 二叉树的最大深度&面试题55
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
left = self.maxDepth(root.left)
right = self.maxDepth(root.right)
return max(left,right)+1
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root: return 0
left = self.minDepth(root.left)
right = self.minDepth(root.right)
if not left or not right: return left+right+1
return min(left,right)+1
节点有关
199. 二叉树的右视图
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:
1 <---
/ \
2 3 <---
\ \
5 4 <---
class Solution:
def rightSideView(self, root: TreeNode) -> List[int]:
if not root:
return []
res = []
tmp_layer = collections.deque() # 还是BFS用到的queue
tmp_layer.append(root) # 先将初始节点压入
while len(tmp_layer) > 0:
count = 0 # 计数,用来记录是某一层的第几个元素
next_layer = []
while len(tmp_layer) > 0: # 处理某一层的节点
tmp_node = tmp_layer.popleft()
if count == 0: # 根据题意只要最右侧的元素,所以根据下面先压入队列的是右子树,后压入左子树,我们只要队列中的第一个元素
res.append(tmp_node.val)
count += 1
# 将某个节点的右左子树压入
if tmp_node.right is not None:
next_layer.append(tmp_node.right)
if tmp_node.left is not None:
next_layer.append(tmp_node.left)
tmp_layer = collections.deque(next_layer) # 更新下一层的tmp
return res
404. 左叶子之和
class Solution:
def sumOfLeftLeaves(self, root: TreeNode) -> int:
if not root:
return 0
if root and root.left and not root.left.left and not root.left.right:
return root.left.val+self.sumOfLeftLeaves(root.right)
return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
68 - II 二叉树的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root.val == p.val or root.val == q.val: return root
l = self.lowestCommonAncestor(root.left, p , q)
r = self.lowestCommonAncestor(root.right, p , q)
return root if l and r else l or r
637. 二叉树的层平均值-BFS
一棵树每层节点的平均数
输入:
3
/ \
9 20
/ \
15 7
输出: [3, 14.5, 11]
解释:
第0层的平均值是 3, 第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11].
class Solution:
def averageOfLevels(self, root: TreeNode) -> List[float]:
if not root: return []
ans = []
queue = [root]
while queue:
n = len(queue)
sum = 0
for i in range(n):
node = queue.pop(0)
sum += node.val
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
ans.append(sum/n)
return ans
513. 找树左下角的值-BFS
给定一个二叉树,在树的最后一行找到最左边的值。
class Solution:
def findBottomLeftValue(self, root: TreeNode) -> int:
queue = [root]
while queue:
node = queue.pop(0)
if node.right: queue.append(node.right)
if node.left: queue.append(node.left)
return node.val
222. 完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
如果让你数一下一棵普通二叉树有多少个节点,这很简单,只要在二叉树的遍历框架上加一点代码就行了。
这个算法的时间复杂度应该是 O(logN*logN)
首先要明确一下两个关于二叉树的名词「完全二叉树」和「满二叉树」。
如果是一个普通二叉树,显然只要向下面这样遍历一边即可,时间复杂度 O(N):
def count(self,root):
if not root: return 0
return 1+self.count(root.left)+self.count(root.right)
那如果是一棵满二叉树,节点总数就和树的高度呈指数关系,时间复杂度 O(logN):
def count(self,root):
h = 0
while root:
root = root.left
h += 1
return 2**h-1
#节点总数就是 2^h - 1
完全二叉树比普通二叉树特殊,但又没有满二叉树那么特殊,计算它的节点总数,可以说是普通二叉树和完全二叉树的结合版,降低时间复杂度
def count(self,root):
hl,hr = 0,0
left,right = root,root
while left:
left = left.left
hl +=1
while right:
right = right.right
hr += 1
if hl == hr: #如果左右子树的高度相同,则是一棵满二叉树
return 2**hl-1
#如果左右高度不同,则按照普通二叉树的逻辑计算
return 1+self.count(root.right)+self.count(root.right)
这个算法的时间复杂度是 O(logN*logN),这是怎么算出来的呢?
之前的 while 需要 logN 的时间,最后要 O(N) 的时间向左右子树递归:
return 1 + countNodes(root.left) + countNodes(root.right);
关键点在于,这两个递归只有一个会真的递归下去,另一个一定会触发hl == hr而立即返回,不会递归下去。
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
由于完全二叉树的性质,其子树一定有一棵是满的,所以一定会触发hl == hr,只消耗 O(logN) 的复杂度而不会继续递归。
算法的递归深度就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logN*logN)。
路径有关
112. 判断路径和是否等于一个数
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
class Solution:
def hasPathSum(self, root, sum):
if not root:
return False
sum -= root.val
if not root.left and not root.right: # if reach a leaf
return sum == 0
return self.hasPathSum(root.left, sum) or self.hasPathSum(root.right, sum)
437.统计路径和等于一个数的路径数量
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> int:
if not root: return 0
ret = self.pathSumStartWithRoot(root,sum)+self.pathSum(root.left,sum)+self.pathSum(root.right,sum)
return ret
def pathSumStartWithRoot(self,root,sum):
if not root: return 0
ret = 0
if root.val==sum:
ret+= 1
ret += self.pathSumStartWithRoot(root.left,sum-root.val)+self.pathSumStartWithRoot(root.right,sum-root.val)
return ret
124 二叉树的最大路径和
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
输入: [-10,9,20,null,null,15,7]
-10
/ \
9 20
/ \</