Leetcode刷题笔记——二叉树篇
一、前言
DFS
是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯,本文没有涉及太多和回溯相关的案例,对回溯相关的可以参考博主的另一篇文章)。
BFS
是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是广度,四面八方的搜索过程。
再讲DFS
之前,我们得好好谈一谈递归,相信大家刚刚接触递归的时候总是一看就会,一写就废,主要是对递归不成体系,没有方法论,每次写递归算法都是靠玄学来写代码
二、递归方法论:递归三部曲
- 确定递归函数的参数和返回值
确定哪些参数是递归的过程中需要处理的,那么就再递归函数中加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型
- 确定终止条件
递归终止条件也叫递归出口
- 确定单层递归的逻辑
确定每一层递归要处理的信息
为了不那么枯燥的介绍代码,这里博主借用了python
的标准库turtle
来绘制一棵二叉树帮助大家更加直观的理解递归,大家将下面的代码复制到自己的本地IDE
运行看看效果
递归演示案例demo
'''绘制分形二叉树'''
import turtle as tl
def draw_branches(tree_length, tree_angle):
'''绘制分形二叉树递归函数'''
if tree_length >= 30: # 树枝长小于30则结束
tl.forward(tree_length) # 往前画树杆、树枝
tl.right(tree_angle) # 往右转(先画右侧树枝)
draw_branches(tree_length * 0.75, tree_angle) # 画下一枝,树枝长降25%
tl.left(2 * tree_angle) # 转向画左,画左侧树枝
draw_branches(tree_length * 0.75, tree_angle) # 画下一枝,树枝长降25%
tl.rt(tree_angle) # 回正到上一枝方向
tl.backward(tree_length) # 然后往回画,回溯到上一层继续画
if __name__ == '__main__':
tl.penup()
tl.right(90) # 调整笔头的方向
tl.goto(0, 200) # 将笔的位置移到窗口的(0, 200)的位置
tl.pendown()
tl.pencolor('brown') # 树干部分为棕色
tree_length = 80 # 设置树干的初始长度为100
tree_angle = 30 # 树枝分叉角度40°,左右各20°
draw_branches(tree_length, tree_angle) # 启动绘图
tl.exitonclick() # 点击退出绘图窗口
三、二叉树中的遍历相关题型
第一题:二叉树的前序遍历
Leetcode144:二叉树的前序遍历:简单题 (详情点击链接见原题)
给你二叉树的根节点
root
,返回它节点值的 前序 遍历
前序遍历:根 左 右
python代码解法:
from typing import List, Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def traversal(self, root, result):
if root:
result.append(root.val)
self.traversal(root.left, result)
self.traversal(root.right, result)
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result, stack = [], [] # stack用来存放结点
self.traversal(root, result)
return result
第二题:二叉树的中序遍历
Leetcode94:二叉树的中序遍历:简单题 (详情点击链接见原题)
给定一个二叉树的根节点
root
,返回 它的 中序 遍历
中序遍历:左 根 右
python代码解法:
class Solution:
def dfs(self, root, res):
if not root:
return
self.dfs(root.left, res) # 先遍历左子树
res.append(root.val) # 添加节点值
self.dfs(root.right, res) # 遍历右子树
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
self.dfs(root, res)
return res
第三题:二叉树的后序遍历
Leetcode145:二叉树的后序遍历:简单题 (详情点击链接见原题)
给你一棵二叉树的根节点
root
,返回其节点值的 后序遍历
后序遍历:左 右 根
python代码解法1:
class Solution:
def dfs(self, root, res):
if not root:
return
self.dfs(root.left, res) # 先遍历左子树
self.dfs(root.right, res) # 遍历右子树
res.append(root.val) # 添加节点值
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
self.dfs(root, res)
return res
python代码解法2(简化后):
对新手不太友好的写法
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
第四题:二叉树的最大深度
Leetcode104:二叉树的最大深度:简单题 (详情点击链接见原题)
解题思路
- 确定递归函数和的参数和返回值:参数就是传入树的根节点,返回一个整数表示树的深度
- 确定终止条件:如果为空节点的话返回
0
表示高度为0
【当root
为空,说明已经越过叶节点,返回深度0
】 - 确定单层递归测逻辑:先求左子树的深度,再求右子树的深度,最后取左右深度的最大数值再
+1
即当前节点的深度 【本质上是对树做后续遍历】
python代码解法:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def calculateDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left = self.calculateDepth(root.left)
right = self.calculateDepth(root.right)
return max(left, right) + 1
第五题:二叉树的最小深度
Leetcode111:二叉树的最小深度:简单题 (详情点击链接见原题)
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量
解题思路
- 递归结束条件【当
root
节点的左右孩子都为空时返回1
】 - 当
root
节点的左右孩子有一个为空时,返回不为空的孩子节点的深度 - 当
root
节点的左右孩子都不为空时,返回左右孩子较小深度的节点值
python代码解法(前序遍历解法):
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
if not root.left and not root.right: # 1.当 root 节点的左右孩子都为空时返回1
return 1
min_depth = 0
if root.left and root.right: # 2.当root节点的左右孩子都不为空时,返回左右孩子较小深度的节点值
min_depth = 1 + min(self.minDepth(root.left), self.minDepth(root.right))
if root.right and not root.left: # 3.当root节点的左右孩子有一个为空时,返回不为空的孩子节点的深度
min_depth = 1 + self.minDepth(root.right)
if root.left and not root.right:
min_depth = 1 + self.minDepth(root.left)
return min_depth
python代码解法(后序遍历解法):
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left = self.minDepth(root.left) # 左
right = self.minDepth(root.right) # 右
# 中间的处理逻辑
if not root.left and root.right: # 遇到右子树不为空
return 1 + right
if root.left and not root.right: # 遇到左子树不为空
return 1 + left
if root.left and root.right: # 遇到左右子树都不为空
return min(left, right) + 1
if not root.left and not root.right: # 遇到叶子结点
return 1
第六题:二叉树的直径
Leetcode543. 二叉树的直径:简单题 (详情点击链接见原题)
给你一棵二叉树的根节点,返回该树的 直径
解题思路
一条路径的长度为该路径经过的结点数 -1
,所以求直径等效于求路径经过结点数的最大值减 1
,所以求直径(路径长度的最大值),等效于求路径经过结点数的最大值减 1
,而任意一条路径可以被看做由某个结点为起点,从其左子树和右子树向下遍历的路径拼接得到
python代码解法(后序遍历解法):
class Solution:
def __init__(self):
self.ans = 0 # 用于记录二叉树的最大直径
def depth(self, node):
if not node:
return 0
left = self.depth(node.left) # 以左子树的根节点为根时往下的最大路径
right = self.depth(node.right)
self.ans = max(self.ans, left + right)
return max(left, right) + 1
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.depth(root)
return self.ans
第七题:完全二叉树的节点个数
Leetcode222. 完全二叉树的节点个数:简单题 (详情点击链接见原题)
给你一棵 完全二叉树 的根节点
root
,求出该树的节点个数
解法1:把它当成一颗普通二叉树,利用后续遍历的处理逻辑
python代码解法:
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left = self.countNodes(root.left)
right = self.countNodes(root.right)
return left + right + 1
解法2:利用完全二叉树的特性
如果子树是满二叉树的话,直接用公式来计算,那如何判断子树是满二叉树呢?
python代码解法:
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left = root.left
right = root.right
left_depth, right_depth = 0, 0
while left:
left = left.left
left_depth += 1
while right:
right = right.right
right_depth += 1
if left_depth == right_depth:
return (2 << left_depth) - 1
return self.countNodes(root.left) + self.countNodes(root.right) + 1
第八题:相同的树
Leetcode100. 相同的树:简单题 (详情点击链接见原题)
给你两棵二叉树的根节点
p
和q
,编写一个函数来检验这两棵树是否相同
解题思路
- 如果两个二叉树都为空则两个二叉树相同
- 如果两个二叉树中只有一个为空,则两个二叉树一定不相同
- 如果两个二叉树都不为空,则首先判断它们根节点的值是否相同,若不相同则两个二叉树一定不同,若相同则递归地判断左子树和右子树是否相同
python代码解法:
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if not p and not q: # 如果两个二叉树都为空则两个二叉树相同
return True
if not (p and q): # 如果两个二叉树中只有一个为空,则两个二叉树一定不相同
return False
if p and q:
if p.val == q.val:
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
else:
return False
第九题:合并二叉树
Leetcode617. 合并二叉树:简单题 (详情点击链接见原题)
给你两棵二叉树:
root1
和root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树
python代码解法:
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if not root1:
return root2
if not root2:
return root1
root1.val += root2.val
root1.left = self.mergeTrees(root1.left, root2.left)
root1.right = self.mergeTrees(root1.right, root2.right)
return root1
第十题:翻转二叉树
Leetcode226. 翻转二叉树:简单题 (详情点击链接见原题)
给你一棵二叉树的根节点
root
,翻转这棵二叉树,并返回其根节点
python代码解法:
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return root
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
第十一题:对称二叉树
Leetcode101. 对称二叉树:简单题 (详情点击链接见原题)
给你一个二叉树的根节点
root
, 检查它是否轴对称
解题思路
-
确定递归函数和的参数和返回值:
因为我们要比较的是根结点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树结点和右子树结点 -
确定终止条件:
- 左结点为空,右结点不为空,左结点不为空,右结点为空
- 左结点和右结点均为空
- 左右结点不为空但值不相等
- 确定单层递归测逻辑:
对每棵子树而言均为后序遍历的处理逻辑
- 比较外侧【左子树的左结点和右子树的右节点】
- 比较内侧【左子树的右结点和右子树的左节点】
python代码解法:
class Solution:
def compare(self, left, right):
# 左结点为空,右结点不为空,左结点不为空,右结点为空
if (not left and right) or (left and not right):
return False
# 左结点和右结点均为空
elif not left and not right:
return True
# 左右结点不为空但值不相等
elif left.val != right.val:
return False
outside = self.compare(left.left, right.right) # 比较外侧:左子树:左,右子树:右
inside = self.compare(left.right, right.left) # 比较内侧:左子树:右,右子树:左
return inside and outside # 处理结点
def checkSymmetricTree(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
return self.compare(root.left, root.right)
第十二题:二叉树的最近公共祖先
Leetcode236. 二叉树的最近公共祖先:中等题 (详情点击链接见原题)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先
解题思路:
若 root
是 p
,q
的最近公共祖先,则只能为以下情况之一:
情况1:如果找到一个结点,发现左子树出现结点p(q)
,右子树出现结点q(p)
,那么该结点就是 结点p
和q
的最近公共祖先
情况2:结点本身 p(q),它拥有一个子孙结点 q(p)
python代码解法:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root == q or root == p or not root:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left and not right:
return root
if not left and right:
return right
elif left and not right:
return left
第十三题:二叉树展开为链表
Leetcode114. 二叉树展开为链表:中等题 (详情点击链接见原题)
给你二叉树的根结点
root
,请你将它展开为一个单链表
具体做法:
- 对于当前结点,如果左子节点不为空,则在其左子树中找到最右边的结点作为前驱结点
- 然后将当前结点的右子节点赋给前驱节点的右子节点
- 再将当前结点的左子节点赋给当前结点的右子节点,并将当前结点的左子节点设为空
- 对当前结点处理结束后,继续处理链表中的下一个结点直到所有结点都处理结束
python代码解法:
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
"""
Do not return anything, modify root in-place instead.
"""
cur = root
while cur:
if cur.left:
pre = cur.left
while pre.right: # 找到左子树最右边的结点
pre = pre.right
pre.right = cur.right # 将原来的右子树接到左子树最右边的结点
cur.left = None
cur.right = root.right # 将左子树插入到原来右子树的地方
cur = cur.right # 考虑下一个结点
四、二叉树中的回溯
第一题:路径总和
Leetcode112:路径总和:简单题 (详情点击链接见原题)
给你二叉树的根节点
root
和一个表示目标和的整数targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和targetSum
。如果存在,返回true
;否则,返回false
解题思路
- 确定递归函数和的参数和返回值:
参数:根节点root
,目标和targetSum
,记录路径上的和total
返回值:如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回 - 确定终止条件:
total == targetSum
- 确定单层递归测逻辑:因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了,递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回
python代码解法:
class Solution:
def dfs(self, root, targetSum, total):
if not (root.left or root.right):
if total == targetSum:
return True
if root.left:
total += root.left.val
if self.dfs(root.left, targetSum, total):
return True
total -= root.left.val
if root.right:
total += root.right.val
if self.dfs(root.right, targetSum, total):
return True
total -= root.right.val
return False
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
return self.dfs(root, targetSum, root.val)
第二题:二叉树的所有路径
Leetcode257. 二叉树的所有路径:简单题 (详情点击链接见原题)
给你一个二叉树的根节点
root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径
python代码解法:
class Solution:
def dfs(self, root, path, res):
if not (root.left or root.right):
str_path = '->'.join(map(str, path))
res.append(str_path)
return
if root.left:
path.append(root.left.val)
self.dfs(root.left, path, res)
path.pop(-1)
if root.right:
path.append(root.right.val)
self.dfs(root.right, path, res)
path.pop(-1)
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
path, res = [root.val], []
if not root:
return res
self.dfs(root, path, res)
return res
第三题:路径总和II
Leetcode113:路径总和II:中等题 (详情点击链接见原题)
给你二叉树的根节点
root
和一个整数目标和targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径
解题思路
本题与上题的区别是要遍历整个树,找到所有路径,所以递归函数不要返回值!
这两题详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。这两道题目是掌握这一知识点非常好的题目,大家看完本篇文章再去做题,就会感受到搜索整棵树和搜索某一路径的差别
本题是典型的回溯问题,解法为:先序遍历 + 路径记录
- 初始化结果列表
res
和路径列表path
python代码解法:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def dfs(self, root, targetSum, total, path, res):
if not (root.left or root.right): # 当root为叶节点
if total == targetSum: # 且路劲和total等于目标值targetSum
res.append(path[:]) # 则将此路径 path 加入 res 中(避免直接添加 path 对象而是拷贝了一个 path 对象并加入到了 res)
return
# 先序遍历
if root.left: # 递归左子节点(空节点不遍历)
total += root.left.val
path.append(root.left.val)
self.dfs(root.left, targetSum, total, path, res) # 递归
path.pop(-1) # 向上回溯前,需要将当前节点从路径 path 中删除
total -= root.left.val # 回溯
if root.right: # 递归右子节点(空节点不遍历)
total += root.right.val
path.append(root.right.val)
self.dfs(root.right, targetSum, total, path, res) # 递归
path.pop(-1) # 向上回溯前,需要将当前节点从路径 path 中删除
total -= root.right.val # 回溯
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
res, path = [], [root.val] # 把根节点放进路径
if not root:
return res
self.dfs(root, targetSum, root.val, path, res)
return res
第四题:路径总和 III
Leetcode437. 路径总和 III:中等题 (详情点击链接见原题)
给定一个二叉树的根节点
root
,和一个整数targetSum
,求该二叉树里节点值之和等于targetSum
的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)
解题思路一(暴力解法):树的遍历 + DFS
一个朴素的做法是搜索以每个结点为根的【往下的】所有路径,并对路径总和为 targetSum
的路径进行累加统计,使用 前序遍历 来搜索所有结点,对前序遍历中的每个当前结点,使用回溯搜索以其为根的所有往下的路径,同时累加路径总和为 targetSum
的所有路径
python代码解法:
class Solution:
def backtraking(self, node, total, targetSum, res):
if total == targetSum:
res.append(1)
if node.left:
self.backtraking(node.left, total + node.left.val, targetSum, res)
if node.right:
self.backtraking(node.right, total + node.right.val, targetSum, res)
def preorder(self, root, targetSum, res):
if not root:
return
self.backtraking(root, root.val, targetSum, res)
self.preorder(root.left, targetSum, res)
self.preorder(root.right, targetSum, res)
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
res = []
self.preorder(root, targetSum, res)
return len(res)
解题思路二:树的遍历 + 前缀和
在完整路径中找到有多少个结点到当前结点的路径总和为 targetSum
,这个树上问题彻底转换为一维问题,求解从原始起点(根结点)到当前结点 b
的路径中,有多少结点 a
满足
具体做法:
我们可以在进行树的遍历时,记录下从原始根节点 root
到当前结点 cur
的路径中,从 root
到任意中间结点 x
的路径总和,结合哈希表,快速找到满足以 cur
为 路径结尾的,使得路径总和为 targetSum
的目标起点有多少个
第五题:求根节点到叶节点数字之和
Leetcode129. 求根节点到叶节点数字之和:中等题 (详情点击链接见原题)
给你一个二叉树的根节点
root
,树中每个节点都存放有一个0
到9
之间的数字
解题思路1:
先用 path
收集结果,再累加求和【隐含二叉树的前序遍历的思想】
python代码解法:
class Solution:
def backtracking(self, node, path, res):
if not node.left and not node.right:
path.append(node.val)
res.append("".join(map(str, path)))
if node.left:
self.backtracking(node.left, path + [node.val], res)
if node.right:
self.backtracking(node.right, path + [node.val], res)
def sumNumbers(self, root: Optional[TreeNode]) -> int:
path, res = [], []
self.backtracking(root, path, res)
total = 0
for num in res:
total += int(num)
return total
python代码解法:
class Solution:
def dfs(self, root, pre_sum, res):
if not root: # 1.递归出口
return 0
total = pre_sum * 10 + root.val # 父节点对应的数字乘以10+该节点的值
if not root.left and not root.right: # 2.如果遍历到叶子节点返回从根节点到叶子节点组成的数字
return total
else:
return self.dfs(root.left, total) + self.dfs(root.right, total)
def sumNumbers(self, root: Optional[TreeNode]) -> int:
res = self.dfs(root, 0)
return res
第六题:二叉树中的最大路径和
Leetcode124. 二叉树中的最大路径和:困难题 (详情点击链接见原题)
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点
python代码解法:
class Solution:
def __init__(self):
self.max_sum = -sys.maxsize # 用来记录最大路径和
def dfs(self, root):
if not root:
return 0
left = self.dfs(root.left)
right = self.dfs(root.right)
inner_max_sum = left + root.val + right # 当前子树内部的最大路径和
self.max_sum = max(self.max_sum, inner_max_sum)
result = root.val + max(0, left, right) # 当前子树对外提供的最大和
return 0 if result < 0 else result
def maxPathSum(self, root: Optional[TreeNode]) -> int:
self.dfs(root)
return self.max_sum
二、二叉搜索树的相关题型
第一题:将有序数组转换为二叉搜索树
Leetcode108. 将有序数组转换为二叉搜索树:简单题 (详情点击链接见原题)
给你一个整数数组
nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树
解题思路:
根据数组构造二叉树,本质就是寻找分割点,分割点作为当前结点,然后递归左区间和右区间
分割点就是数组中间位置的结点,如果数组长度为偶数,中间结点有两个【取哪一个都可以,只不过构成了不同的平衡二叉搜索树】
python代码解法:
class Solution:
def traversal(self, nums, left, right):
if left > right:
return None
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = self.traversal(nums, left, mid - 1)
root.right = self.traversal(nums, mid + 1, right)
return root
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
root = self.traversal(nums, 0, len(nums) - 1)
return root
第二题:二叉搜索树中第K小的元素
Leetcode230. 二叉搜索树中第K小的元素:中等题 (详情点击链接见原题)
给定一个二叉搜索树的根节点
root
,和一个整数k
,请你设计一个算法查找其中第k
个最小元素(从1
开始计数)
在二叉搜索树中,任意子结点都满足 左子结点 < 根结点 < 右子结点 的规则,因此二叉搜索树的一个重要性质: 二叉搜索树的中序遍历为递增序列, 本题转换为求中序遍历的第 k
个结点
python代码解法:
class Solution:
def __init__(self):
self.res = 0
self.k = 0
def dfs(self, node):
if not node:
return
self.dfs(node.left)
self.k -= 1 # 统计当前结点的序号
if self.k == 0:
self.res = node.val # 遍历到第 k 个结点应记录下结果
return # 后续的遍历失去意义,提前返回
self.dfs(node.right)
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
self.res = 0
self.k = k
self.dfs(root)
return self.res
第三题:验证二叉搜索树
Leetcode98.验证二叉搜索树:中等题 (详情点击链接见原题)
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树
解题思路:
在中序遍历下,输出二叉搜索树结点的数值是有序序列,有了这个特性,验证二叉搜索树就相当于变成了判断一个序列是不是递增的了
解法1:借助辅助空间保存中序遍历的遍历序列
python代码解法:
class Solution:
def inOrder(self, root, res):
if root:
self.inOrder(root.left, res)
res.append(root.val)
self.inOrder(root.right, res)
def isValidBST(self, root: Optional[TreeNode]) -> bool:
res = []
self.inOrder(root, res)
for i in range(1, len(res)):
if res[i] <= res[i - 1]:
return False
return True
解法2:在递归的过程中直接判断是否有序
python代码解法:
class Solution:
def __init__(self):
self.min_val = -sys.maxsize
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left = self.isValidBST(root.left)
if self.min_val < root.val:
self.min_val = root.val
else:
return False
right = self.isValidBST(root.right)
return left and right
三、二叉树的构建
第一题:从前序与中序遍历序列构造二叉树
Leetcode105. 从前序与中序遍历序列构造二叉树:中等题 (详情点击链接见原题)
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点
前序遍历:根结点 | 左子树 | 右子树
中序遍历:左子树 | 根结点 | 右子树
推论
- 前序遍历的首元素为树的根节点
root
的值 - 在中序遍历中搜索根节点
root
的索引,根据索引可将中序遍历划分为【左子树 | 根结点 | 右子树】
- 根据中序遍历中的左右子树的结点数量,可将前序遍历划分为
【左子树 | 根结点 | 右子树】
解题思路
- 确定递归函数和的参数和返回值:
- 根节点索引:根节点在前序遍历中的索引
root_index
- 子树在中序遍历中的左边界
left
- 子树在中序遍历中的右边界
right
- 确定终止条件:当
left > right
时代表已经越过叶节点,此时返回 - 确定单层递归测逻辑:
建立根节点
node
,结点值为preorder[root_index]
划分左右子树:查找根节点preorder[root_index]
在中序遍历inorder
中的索引i
(要用到哈希)
构建左右子树:开启左右子树递归
python代码解法:
class Solution:
def __init__(self):
self.hash_map = {}
self.preorder = []
self.inorder = []
def dfs(self, pre_left, pre_right, in_left, in_right):
if pre_left > pre_right:
return None
in_root = self.hash_map[self.preorder[pre_left]] # 找到根节点在中序遍历中的索引
root = TreeNode(self.preorder[pre_left]) # 先把根结点创建出来
left_size = in_root - in_left # 得到左子树中的结点数目
root.left = self.dfs(pre_left + 1, pre_left + left_size, in_left, in_root - 1)
root.right = self.dfs(pre_left + 1 + left_size, pre_right, in_root + 1, in_right)
return root
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
for index, value in enumerate(inorder):
self.hash_map[value] = index
self.preorder = preorder
self.inorder = inorder
return self.dfs(0, len(preorder) - 1, 0, len(inorder) - 1)
注意:
self.dfs(i - left + root_index + 1, i + 1, right, pre_order, dic)
中i - left + root_index + 1
的含义为根节点索引 + 左子树长度 + 1
第二题:从前序与中序遍历序列构造二叉树
Leetcode106. 从中序与后序遍历序列构造二叉树:中等题 (详情点击链接见原题)
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树
python代码解法:
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if not postorder:
return None
root_val = postorder[-1]
root = TreeNode(root_val)
sep_index = inorder.index(root_val)
# 第四步:切割inorder数组,得到inorder数组的左右半边
inorder_left = inorder[:sep_index]
inorder_right = inorder[sep_index + 1:]
# 第五步:(利用中序的左右两边的长度来)切割postorder数组,得到postorder数组的左右半边
postorder_left = postorder[:len(inorder_left)]
postorder_right = postorder[len(inorder_left): len(postorder) - 1]
# 第六步: 递归
root.left = self.buildTree(inorder_left, postorder_left)
root.right = self.buildTree(inorder_right, postorder_right)
return root
四、总结
本文给大家总结了二叉树中的DFS
,二叉树本身就是一种递归定义的数据结构,所以二叉树和DFS
是紧密相关的,其中的递归思想又诞生了很多衍生题,比如大多数的网格问题都可以看成是二叉树问题的一种变体【四叉结构】,这不是靠死记硬背就能解决的,需要大家在熟悉二叉树的性质的同时对类相关知识点的串联分析,这正好是很多大厂面试中面试官特别喜欢考察的面试者的点,最后,新的一年祝大家学习,工作一切顺利,找到心仪的offer,跳槽涨薪double,迎娶白富美,早日走向人生巅峰~