基础算法精讲·题目汇总:灵茶山艾府 - 【基础算法精讲】- GitHub
视频:灵茶山艾府的个人空间-灵茶山艾府个人主页-哔哩哔哩视频
09 递归 数学归纳法 栈
课程讲解
在做二叉树题目的时候,往往需要用到递归
104. 二叉树的最大深度
定义:根节点到最远叶子节点的最长路径上的节点数



如何思考递归
写循环就是在重复执行同样一份代码,每次循环都是加在同一个变量(放在循环外)
而递归处理的问题是有嵌套关系的,需要把计算结果返给它的上一级问题,以此类推(类似数学归纳法)
- 从原问题出发,把问题不断分解成更小的子问题 ------ 递
- 不断递下去总会有尽头,即边界条件,返回答案 ------ 归
本题中的递归条件就是空节点,可以直接返回0作为答案
【递归写法】
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
left_depth = self.maxDepth(root.left)
right_depth = self.maxDepth(root.right)
return max(left_depth, right_depth) + 1
- 时间复杂度:O(n),每个节点都遍历了一次
- 空间复杂度:O(n)。return 的时候怎么知道要 return 到哪个节点上去,需要一个数据结构,支持最后放进去的节点最先出来,即栈。最坏情况下,二叉树只有左儿子,没有右儿子,就变成了链状结构,此时栈的大小就是 O(n)
另一种写法:还可以把路径上的节点个数也传下去
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
res = 0
def f(node, cnt): # cnt为经过的节点个数
if node is None:
return
cnt += 1 # 每到达一个节点都要+1
nonlocal res
res = max(res, cnt)
f(node.left, cnt)
f(node.right, cnt)
f(root, 0)
return res
# 下面这种写法本人更习惯,思路是一样的
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
self.res = 0
self.dfs(root, 0)
return self.res
def dfs(self, node, cnt):
if not node:
return
cnt += 1
self.res = max(self.res, cnt)
self.dfs(node.left, cnt)
self.dfs(node.right, cnt)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
【层序遍历写法】
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
ans = []
q = deque([root])
while q:
vals = []
for _ in range(len(q)):
node = q.popleft()
vals.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
ans.append(vals)
return len(ans)
课后作业
10 相同 对称 平衡 右视图
课程讲解
100. 相同的树
怎么定义相同?根节点必须相同、左子树和右子树必须相同
递归的边界条件:有一棵为空就无法递归,如果两个节点都是空,则返回True,否则返回False
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
101. 对称二叉树
把这棵树拆成左右两棵树
- 看根节点是否相同
- 然后递归左边的左子树和右边的右子树,以及左边的右子树和右边的左子树,看是否相同
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# 复用上一题的函数
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.right) and self.isSameTree(p.right, q.left) # diff
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
return self.isSameTree(root.left, root.right)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
110. 平衡二叉树
**高度平衡:**左子树和右子树高度差的绝对值不超过1。如下图,左子树的高度为3,右子树的高度为1,则不平衡

通过递归得到左右两棵子树的高度,算一下高度差的绝对值是否小于等于1。如果在递归过程中发现这棵树是不平衡的,可以返回-1(表示这棵树不平衡),就不再继续递归了
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
return self.get_height(root) != -1
def get_height(self, node):
if node is None:
return 0
left_height = self.get_height(node.left)
if left_height == -1:
return -1
right_height = self.get_height(node.right)
if right_height == -1 or abs(left_height - right_height) > 1:
return -1
return max(left_height, right_height) + 1
- 时间复杂度:O(n)
- 空间复杂度:O(n)
199. 二叉树的右视图
先递归右子树,再递推左子树
在递归的同时记录一个节点个数或者说递归深度,如果递归深度等于答案长度,那么这个节点就需要记录到答案中;否则就相当于被右边挡住了,不记录到答案中

python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
self.ans = []
self.dfs(root, 0)
return self.ans
def dfs(self, node, depth):
if node is None:
return
if depth == len(self.ans):
self.ans.append(node.val)
self.dfs(node.right, depth+1)
self.dfs(node.left, depth+1)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
课后作业
11 二叉搜索树 前序 中序 后序
课程讲解
98. 验证二叉搜索树
三种做法:
- 前序遍历:先判断,再递归
- 中序遍历:大于上一个节点
- 后序遍历:先递归,再判断
**二叉搜索树定义:**对于一个节点来说,它的左子树所有节点值都小于它的值,它的右子树所有节点值都大于它的值。同时,它的左子树和右子树也必须是二叉搜索树
【前序遍历写法】
递归时需要传入节点 和开区间的范围,先判断节点值是否在开区间内,然后再往下递归
- 如果往左边递归,就把开区间的右边界更新为节点值
- 如果往右边递归,就把开区间的左边界更新为节点值
- 这种先访问节点值,再递归左右子树的做法叫【前序遍历】
- 特别注意,根节点(函数调用的入口)再往上是没有节点的,这种情况下需要传入负无穷和正无穷

python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# def check(self, node, left, right):
def isValidBST(self, root: Optional[TreeNode], left=-inf, right=inf) -> bool:
if root is None:
return True
x = root.val
return left < x < right and self.isValidBST(root.left, left, x) and self.isValidBST(root.right, x, right)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
【中序遍历写法】
**【中序遍历】**如果按照递归左子树访问节点值,再递归右子树这样的顺序去遍历,就可以得到一个严格递增数组
- 对于二叉搜索树来说,按照左子树 -> 节点值 -> 右子树这样的顺序遍历
- 遍历完左子树,就可以看当前的节点值是否 > 上一个节点的值
- 然后把当前节点值记录下来,和下一个节点值比较大小
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
pre = -inf # 第一个节点没有上一个节点,初始化为-inf
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if root is None:
return True
if not self.isValidBST(root.left): # 递归左子树
return False
if root.val <= self.pre:
return False
self.pre = root.val
return self.isValidBST(root.right) # 递归右子树
- 时间复杂度:O(n)
- 空间复杂度:O(n)
【后序遍历写法】
前序遍历是把节点值的范围往下传,还可以把节点值的范围往上传。需要先遍历左右子树,再判断节点值,这就是**【后序遍历】**
- 最小值和最大值都需要返回
- 为了简化代码逻辑,可以递归到空节点,返回 正无穷到负无穷
- 如果中途发现它不是一棵二叉搜索树,可以返回 负无穷到正无穷(表示False)

python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def dfs(node):
if node is None:
return inf, -inf
l_min, l_max = dfs(node.left) # 递归左子树
r_min, r_max = dfs(node.right) # 递归右子树
x = node.val
if x <= l_max or x >= r_min: # 不是一棵二叉搜索树
return -inf, inf
return min(l_min, x), max(r_max, x)
return dfs(root)[1] != inf
- 时间复杂度:O(n)
- 空间复杂度:O(n)
课后作业
12 最近公共祖先
课程讲解
一个节点也可以是它自己的祖先
236. 二叉树的最近公共祖先
找p和q两个节点的最近公共祖先
- 对于当前节点是空节点的情况,直接返回空节点
- 如果当前节点是p或q,不需要继续递归寻找了
- 以上情况之外,就递归左右子树
- 如果左右子树都找到了,那么最近公共祖先就是当前节点
- 如果只有左子树找到了,那么最近公共祖先肯定在左子树中,所以返回递归左子树后的结果就行
- 右子树同理


python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root is None or root is p or root is q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
if left:
return left
return right
- 时间复杂度:O(n)
- 空间复杂度:O(n)
235. 二叉搜索树的最近公共祖先
可以利用二叉搜索树的性质:左子树的节点值都比当前节点值小,右子树的节点值都比当前节点值大
- 如果p和q的节点值都小于当前节点值,说明p和q都在左子树中,所以最近公共祖先也在左子树中,递归左子树就好了
- 如果p和q的节点值都大于当前节点值,说明p和q都在右子树中,所以最近公共祖先也在右子树中,递归右子树就好了
- 如果p和q分别在左右子树中,那么最近公共祖先就是当前节点
- 当前节点是p或者q,那么最近公共祖先也是当前节点
- 无论什么情况,当前节点都不会是空节点(题目说了p和q一定在给定的二叉搜索树中),因此不需要判断当前节点是空节点

python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
x = root.val
if p.val < x and q.val < x:
return self.lowestCommonAncestor(root.left, p, q)
if p.val > x and q.val > x:
return self.lowestCommonAncestor(root.right, p, q)
return root
- 时间复杂度:O(n)
- 空间复杂度:O(n)。题目没有说这棵树是平衡树,在最坏情况下,这棵树会退化成一条链
课后作业
13 层序遍历 BFS 队列
二叉树的层序遍历,也叫广度优先搜索
- 递归相当于是从根节点出发,径直往下走
- 层序遍历是一行一行地遍历
计算机在执行递归的时候,会用一个栈来维护入栈出栈的过程,这些操作计算机都帮你完成了,所以代码写起来很简洁;
而层序遍历需要手动编写入队出队的代码,相比递归,层序遍历需要写更多代码
课程讲解
102. 二叉树的层序遍历
【双数组写法】
用两个数组 cur 和 nxt







python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
cur = [root]
while cur:
nxt = []
vals = []
for node in cur:
vals.append(node.val)
if node.left:
nxt.append(node.left)
if node.right:
nxt.append(node.right)
cur = nxt
ans.append(vals)
return ans
- 时间复杂度:O(n)。每个节点只会遍历一次
- 空间复杂度:O(n)。由于在满二叉树(每一层都填满)的情况下,最后一层有大约 n/2 个节点
【队列写法】
每一层要循环多少次,其实就是队列的长度。在循环开始时获取队列的长度,然后循环这么多次就可以得到下一层的节点了
Python里可以用双端队列 deque 表示队列







python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
q = deque([root])
while q:
vals = []
for _ in range(len(q)):
node = q.popleft()
vals.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
ans.append(vals)
return ans
103. 二叉树的锯齿形层序遍历
只需要把偶数层的数组翻转一下就好了
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
q = deque([root])
# 需要一个布尔值来表示当前是否为偶数层
even = False # 一开始是奇数层,所以初始化为False
while q:
vals = []
for _ in range(len(q)):
node = q.popleft()
vals.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
ans.append(vals[::-1] if even else vals) # 偶数层就翻转一下,奇数层就不变
even = not even
return ans
- 时空复杂度都是 O(n)
513. 找树左下角的值
最底层最左边的节点值
做法1:返回最后一层的第一个节点
做法2:把层序遍历改成从右到左遍历 (只需要改变一下节点入队的顺序即可,先把右孩子入队,再把左孩子入队;出队也是右孩子先出队,然后左孩子再出队),那么最后一个出队的节点值就是答案
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
q = deque([root])
while q:
# 只需要知道最后一个节点即可,因此此处不用再写二重循环
node = q.popleft()
# 每一层从右到左
if node.right:
q.append(node.right)
if node.left:
q.append(node.left)
return node.val # 最后一个节点就是答案
- 时空复杂度都是 O(n)