二叉树
LeetCode二叉树刷题记录
基础知识
二叉树:树中各个节点的度小于等于2的有序树
二叉树可以分为五种形态
- 空树
- 只有根节点
- 只有左子树
- 只有右子树
- 左右子树都有
种类
满二叉树:除了叶子节点,所有节点的度都为2,叶子节点一定都在最后一层
深度为 k k k的完全二叉树,其节点总数为 2 k − 1 2^k - 1 2k−1
完全二叉树:叶子节点只能出现在最后两层,且最后一层的叶子节点从左往右依次排列
同等节点的二叉树中,完全二叉树的深度最小;节点数为 n n n的完全二叉树可以理解为,同等深度的满二叉树取前 n n n个节点
二叉搜索树:任意节点的左子树若不为空,则左子树节点小于根节点;右子树若不为空,则右子树节点大于根节点
平衡二叉树:是二叉搜索树的一种,任意节点的左右子树高度差不超过1
存储结构
- 顺序存储:保存在数组中,树中的节点一层层往下存储,每层从左到右存储
left = $ 2 \times i + 1$ right = $ 2 \times i + 2$ parent = ( i − 1 ) / / 2 (i - 1) // 2 (i−1)//2 - 链式存储:保存在链表中,每个节点有一个数据域,还有两个指针域,分别指向左子树和右子树
class TreeNode:
def __init__(self,val = 0, left = None, right = None):
self.val = val
self.left = left
self.right = right
遍历
深度优先遍历
尽可能"深"地访问每个分支,直到到达叶子节点为止,然后回溯到上一个节点,遍历另一个分支
- 前序遍历:根节点 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根节点 -> 右子树
- 后序遍历:左子树 -> 右子树 -> 根节点
这里的"前中后"指的是根节点遍历时的顺序
深度优先遍历一般用递归来实现,也可以用栈
递归三要素:
- 确定递归函数的参数和返回值
- 确定递归终止条件
- 单层递归逻辑:假定能解决 k k k规模的问题,如何解决 k + 1 k+1 k+1规模的问题(数学归纳法)
前序遍历
我一开始使用list拼接来做,但这样需要额外的空间(每次递归时都要创建新的list),并不划算。不能直接在result
列表上修改的原因是,result
没有作为参数传入,可以考虑定义一个辅助函数
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
if root == None:
return result
result.append(root.val)
if root.left != None:
resultleft = self.preorderTraversal(root.left)
result = result + resultleft
if root.right != None:
resultright = self.preorderTraversal(root.right)
result = result + resultright
return result
改进版本:
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def preorder(root:Optional[TreeNode],result:List[int]) -> None:
if root == None:
return
result.append(root.val)
preorder(root.left, result) # 如果left为空子树,下一层递归的时候会自动退出的
preorder(root.right, result)
preorder(root,result)
return result
在计算机内部,递归的函数调用就是用栈来实现的,每次递归调用都会将当前状态压入栈中,直到递归结束再弹出栈,因此我们也可以用栈来显示的模拟递归过程
递归栈的调用过程
-
调用
preorder(1)
:- 访问
1
- 调用
preorder(2)
(左子树)
- 访问
-
调用
preorder(2)
:- 访问
2
- 调用
preorder(4)
(左子树)
- 访问
-
调用
preorder(4)
:- 访问
4
- 左子树为空,返回
- 右子树为空,返回
- 访问
-
返回到
preorder(2)
:- 调用
preorder(5)
(右子树)
- 调用
-
调用
preorder(5)
:- 访问
5
- 左子树为空,返回
- 右子树为空,返回
- 访问
-
返回到
preorder(1)
:- 调用
preorder(3)
(右子树)
- 调用
-
调用
preorder(3)
:- 访问
3
- 左子树为空,返回
- 右子树为空,返回
- 访问
如果我们想用显示的栈,来模拟这一调用过程,最需要解决的问题是如何“回退”,比如当调用完preorder(4)
之后,发现左右子树为空,返回上一级调用preorder(2)
,开始遍历右子树,如果用显示的栈,应该如何实现这一个过程呢?
要想能遍历右子树,就需要在调用preorder(2)
的时候把左右子树的信息都暂存到栈里,这样才能在遍历完左子树之后遍历右子树,由于栈是后进先出的数据结构,我们希望先遍历左子树,就要后把左子树的信息存到栈里,因此先存右子树信息,后存左子树信息
栈的数据结构在此处起到一个“记录”的作用,只有保存了当前递归的状态(左右子树的信息)才能回退,完成对树的遍历
显式栈的执行过程:
-
初始化栈:
stack = [1]
-
弹出
1
,访问1
:- 将右孩子
3
压入栈:stack = [3]
- 将左孩子
2
压入栈:stack = [3, 2]
- 将右孩子
-
弹出
2
,访问2
:- 将右孩子
5
压入栈:stack = [3, 5]
- 将左孩子
4
压入栈:stack = [3, 5, 4]
- 将右孩子
-
弹出
4
,访问4
:- 没有孩子,
stack = [3, 5]
- 没有孩子,
-
弹出
5
,访问5
:- 没有孩子,
stack = [3]
- 没有孩子,
-
弹出
3
,访问3
:- 没有孩子,
stack = []
- 没有孩子,
当栈为空的时候表示遍历完成了
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
while stack:
parent = stack.pop()
if parent == None:
break
if parent.right != None:
stack.append(parent.right)
if parent.left != None:
stack.append(parent.left)
result.append(parent.val)
return result
后序遍历
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
def postorder(root:Optional[TreeNode], result:list) -> None:
if root == None:
return
postorder(root.left,result)
postorder(root.right,result)
result.append(root.val)
return result
result = []
postorder(root,result)
return result
我的思路就是从递归的栈直接改写成显示的栈,因为后序遍历是左子树->右子树->根节点,所以根节点先入栈,再右子树,再左子树,等到遍历到左右子树都为空的节点之后开始返回上一层,于是我写出来如下程序
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
while stack:
parent = stack[-1]
if parent == None:
break
if parent.right == None and parent.left == None:
result.append(parent.val)
stack.pop()
continue
if parent.right != None:
stack.append(parent.right)
if parent.left != None:
stack.append(parent.left)
return result
但这个程序有一个地方是错的,当遍历到叶子节点,返回上一层的时候,叶子节点又重新被加入了栈,导致无限死循环,所以需要有一个容器来记录一下哪些被遍历过,哪些没有,遍历过的节点又遍历,说明其左右子树都已经遍历完了,回退到该节点
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
visit = set()
while stack:
parent = stack[-1]
if parent == None:
break
if parent.right == None and parent.left == None or (parent in visit):
result.append(parent.val)
stack.pop()
continue
if parent.right != None:
stack.append(parent.right)
if parent.left != None:
stack.append(parent.left)
visit.add(parent)
return result
中序遍历
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
def inorder(root:Optional[TreeNode], result:List[int]) -> None:
if root == None:
return
inorder(root.left, result)
result.append(root.val)
inorder(root.right,result)
return result
result = []
inorder(root,result)
return result
我的思路就是从递归的栈直接改写成显示的栈,因为中序遍历是左子树->根节点->右子树,所以右子树先入栈,再入根节点,再左子树,等到遍历到左右子树都为空的节点之后开始返回上一层
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
visit = set()
while stack:
parent = stack.pop()
if parent == None:
continue
if (parent.left == None and parent.right == None) or (parent in visit):
result.append(parent.val)
continue
if parent.right != None:
stack.append(parent.right)
stack.append(parent)
if parent.left != None:
stack.append(parent.left)
visit.add(parent)
return result
为什么前序遍历不需要一个visit集合来记录当前节点是否被遍历过,而后序和中序需要呢?因为前序遍历要访问的节点和要处理的节点是一致的(访问节点表示当前获取到指针的节点,访问节点永远是根节点,处理节点表示要输出的节点)所以可以直接result.append(parent.val)
广度优先遍历
逐层访问节点,先访问离起始节点近的,再访问离起始节点远的
层序遍历:层之间从上到下,每层内部从左到右
广度优先遍历一般可以用队列来实现
层序遍历希望一层层输出,即result = [1, 2, 3, 4, 5, 6]
但是二叉树的访问结构决定当我们访问跟节点的时候,我们只能取遍历它的左右孩子节点,而不能遍历到它的兄弟节点,所以我们需要引入队列这种数据结构来辅助,被遍历到的节点都暂存在队列中,当收集完一层的节点后统一输出,一边输出一遍加入下一层的节点
二叉树的层序遍历
class Solution:
from collections import deque
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
result = []
queue = deque()
if root != None:
queue.append(root)
while queue:
size = len(queue)
# 记录每层节点数
tmp = []
while size > 0:
node = queue.popleft()
tmp.append(node.val)
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
result.append(tmp)
return result
二叉树的层序遍历II
返回二叉树自底向上的层序遍
class Solution:
from collections import deque
def levelOrderBottom(self, root: Optional[TreeNode]) -> List[List[int]]:
queue = deque()
result = []
if root != None:
queue.append(root)
while queue:
size = len(queue)
tmp = []
while size > 0:
node = queue.popleft()
tmp.append(node.val)
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
result.append(tmp)
return result[::-1]
二叉树的右视图
想象自己站在二叉树的右侧,返回能看到的节点,也就是返回每层最后一个节点,可以使用层序遍历
class Solution:
from collections import deque
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
queue = deque()
result = []
if root != None:
queue.append(root)
while queue:
size = len(queue)
while size > 0:
node = queue.popleft()
if size == 1:
result.append(node.val)
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
return result
二叉树的层平均值
class Solution:
from collections import deque
def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
queue = deque()
result = []
if root != None:
queue.append(root)
while queue:
size = len(queue)
n = size
sum = 0
while size > 0:
node = queue.popleft()
sum += node.val
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
result.append(sum/n)
return result
N叉树的层序遍历
注意N叉树的节点定义
"""
# Definition for a Node.
class Node:
def __init__(self, val: Optional[int] = None, children: Optional[List['Node']] = None):
self.val = val
self.children = children
"""
class Solution:
from collections import deque
def levelOrder(self, root: 'Node') -> List[List[int]]:
queue = deque()
result = []
if root != None:
queue.append(root)
while queue:
size = len(queue)
tmp = []
while size > 0:
node = queue.popleft()
tmp.append(node.val)
n = 0
while n < len(node.children):
if node.children[n] != None:
queue.append(node.children[n])
n += 1
size -= 1
result.append(tmp)
return result
在每个树行中找最大值
class Solution:
from collections import deque
def largestValues(self, root: Optional[TreeNode]) -> List[int]:
queue = deque()
result = []
if root != None:
queue.append(root)
while queue:
size = len(queue)
max = -float('inf')
while size > 0:
node = queue.popleft()
if node.val > max:
max = node.val
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
result.append(max)
return result
填充每个节点的下一个右侧节点指针
填充每个节点的next
指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将next
指针设置为NULL
class Solution:
from collections import deque
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
queue = deque()
if root != None:
queue.append(root)
while queue:
size = len(queue)
pre = None
while size > 0:
node = queue.popleft()
if pre:
pre.next = node
pre = node
if pre.left != None:
queue.append(pre.left)
if pre.right != None:
queue.append(pre.right)
size -= 1
return root
二叉树的最大深度
class Solution:
from collections import deque
def maxDepth(self, root: Optional[TreeNode]) -> int:
queue = deque()
max = 0
if root != None:
queue.append(root)
while queue:
size = len(queue)
max += 1
while size > 0:
node = queue.popleft()
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
return max
二叉树的层序遍历
class Solution:
from collections import deque
def minDepth(self, root: Optional[TreeNode]) -> int:
queue = deque()
min = 0
if root != None:
queue.append(root)
while queue:
size = len(queue)
min += 1
while size > 0:
node = queue.popleft()
if node.left == None and node.right == None:
# 遇到叶子节点
return min
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
return min
题1:翻转二叉树
二叉树的问题,解题前要想清楚用前中后序遍历,还是层序遍历
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root == None:
return
root.left = self.invertTree(root.left) #翻转左子树
root.right = self.invertTree(root.right) #翻转右子树
root.left, root.right = root.right, root.left # 左右子树翻转
return root
虽然我能写出翻转二叉树,但我开发不出Homebrew啊
注意:此题我用的是后序遍历,即先处理左子树和右子树,最后处理根节点(交换左右子树);也可以采用前序遍历,先交换根节点的左右子树,再处理左子树和右子树,中序遍历就不太行
如果非要中序遍历
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root == None:
return
root.left = self.invertTree(root.left) #翻转左子树
root.left, root.right = root.right, root.left # 左右子树翻转
root.left = self.invertTree(root.left) #翻转左子树,原来的右子树
return root
- 函数返回值和参数:节点
- 终止条件:当前子树为空树
- 每层的处理规则:先 f ( l e f t ) f(left) f(left)交换好左子树,再 f ( r i g h t ) f(right) f(right)交换好右子树,最后交换左右子树,当前树就翻转好了
层序遍历也可以,每层翻转左右子树
class Solution:
from collections import deque
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
queue = deque()
if root != None:
queue.append(root)
while queue:
size = len(queue)
while size > 0:
node = queue.popleft()
node.left, node.right = node.right, node.left # 放前放后无所谓
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
size -= 1
return root
题2:对称二叉树
一开始想,检测每层的"对称性"所以采用了层序遍历,特别要注意的是,如果左子树右子树在某个位置为空,层序遍历的时候那层的这个位置需要用none
来填充,不然的话,如下情况会被误判为对称
class Solution:
from collections import deque
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
queue = deque()
flag = True
if root != None:
queue.append(root)
while queue:
n = len(queue)
tmp = []
while n > 0:
node = queue.popleft()
if node == None:
tmp.append(None)
else:
tmp.append(node.val)
queue.append(node.left)
queue.append(node.right)
n -= 1
if tmp[:] != tmp[::-1]:
# 这层不对称
flag = False
return flag
这个题目也可以用深度优先遍历里面的后序遍历
如何判断一棵树是否是对称二叉树呢?这棵树的左右子树是否对称,那么怎么判断左右子树是否对对称呢?在左右子树根节点相同的基础上,判断左子树的左子树是否和右子树的右子树对称,左子树的右子树是否和右子树的左子树对称
所以假设我有一个函数能判断两个树是否为对称的树,每次只要调用 f ( l e f t r e e . l e f t , r i g h t r e e . r i g h t ) f(leftree.left, rightree.right) f(leftree.left,rightree.right) f ( l e f t r e e . r i g h t , r i g h t r e e . l e f t ) f(leftree.right,rightree.left) f(leftree.right,rightree.left) 如果两次调用得到的结果都是对称,加上当前两个根节点相同,就是对称
典型递归的问题:
- 参数和返回值:要比较两个树是否对称,传入两个树的指针,返回是否对称,布尔类型数据
- 边界条件:如果两个树中有空树
- 每层处理规则:判断tree1的左子树是否和tree2的右子树对称,tree1的右子树是否和tree2的左子树对称
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
def compare(tree1:Optional[TreeNode], tree2:Optional[TreeNode]) -> bool:
if tree1 == None and tree2 == None:
return True
if tree1 == None and tree2 != None:
return False
if tree1 != None and tree2 == None:
return False
flag1 = compare(tree1.left, tree2.right)
flag2 = compare(tree1.right, tree2.left)
return flag1 and flag2 and tree1.val == tree2.val
if root == None:
return True
return compare(root.left, root.right)
注意这是一种后序遍历的逻辑,先处理完子树,再处理本节点,这和tree1.val == tree2.val
这条语句放在哪里无关,这条语句可以放在前面,但是必须得等子树处理好后才能返回,还是理解为后序遍历
也可以用迭代做,思路是一样的。用队列这种数据结构,每次比较一对节点,并且把这对节点中的节点1的左子树加入队列,再把节点2的右子树加入队列,把节点1的右子树加入队列,再把节点2的左子树加入队列 — “从两边到中间”原则
class Solution:
from collections import deque
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if root == None:
return True
queue = deque()
queue.append(root.left)
queue.append(root.right)
while queue:
node1 = queue.popleft()
node2 = queue.popleft()
## 一对对弹出
## node1在同一层中从左到右
## node2在同一层从右到左
## 有点类似层序,不过把比较每层的对称性这一步直接和遍历过程融合了
if node1 == None and node2 == None:
continue
if node1 == None and node2 != None:
return False
if node1 != None and node2 == None:
return False
if node1.val != node2.val:
return False
# 加入下一层节点,不过此处不用明显区分层,直接按顺序比较下去
# 因为节点被配对好了
queue.append(node1.left)
queue.append(node2.right)
queue.append(node1.right)
queue.append(node2.left)
return True
题3:相同的树
感觉比对称好想,直接递归的比较左子树和右子树是否相等就可以。同样用迭代,层序都可以
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p == None and q == None:
return True
if p != None and q == None:
return False
if p == None and q != None:
return False
if q.val != p.val :
return False
return self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)
题4:另一棵树的子树
一开始我想到的是暴力解法,遍历每个节点,检查该节点的子树是否和目标数相等,时间复杂度是
O
(
∣
s
∣
×
∣
t
∣
)
O(\vert s \vert \times \vert t \vert)
O(∣s∣×∣t∣) 其中
∣
s
∣
\vert s \vert
∣s∣表示树s
的节点数量
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
def isSameTree(p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p == None and q == None:
return True
if p != None and q == None:
return False
if p == None and q != None:
return False
if q.val != p.val :
return False
return isSameTree(p.left,q.left) and isSameTree(p.right,q.right)
if subRoot == None:
return True
if root == None and subRoot != None:
return False
if isSameTree(root, subRoot):
return True
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
方法二:同样是暴力搜索,但是不引入辅助函数,代码更简洁
初看这道题,感觉递归的影子非常明显,假设我有一个函数能检查一棵树是否为另一棵的子树,那么我只需要检查f(root.left, subRoot) f(root.right, subRoot) f(root, subRoot) 在试图拆解问题的时候,我尝试把它分为原树的左子树是否包含目标子树,原树的右子树是否包含目标子树,然后?解决当前节点出问题了,难道我验证原树是否包含目标子树吗?这样就不符合递归把大问题拆成小问题的原则,而是把大问题拆分成了两个小问题和一个大问题
为了解决这个问题,把问题拆解成,原树的左子树是否包含目标子树,原树的右子树是否包含目标子树,原树是否和和目标子树相等(包含目标子树,说明在某个节点上,目标子树和原树的子树相等)然后引入一个变量flag
来记录当前是在判断相等还是在检查子树
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode], flag : bool = True) -> bool:
if subRoot == None and root == None:
return True
if subRoot != None and root == None:
return False
if root != None and subRoot == None:
return flag # 是子树但不是相等
result = False
if flag:
# 如果当前在判断子树模式
if root.val == subRoot.val and self.isSubtree(root.left, subRoot.left, False) and self.isSubtree(root.right, subRoot.right, False):
# 判断原树是否为目标子树,进入判断相等模式
return True
# 如果当前节点不匹配,递归检查 root 的左子树或右子树
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
else:
# 当前在判断相等模式
return root.val == subRoot.val and self.isSubtree(root.left, subRoot.left, False) and self.isSubtree(root.right, subRoot.right, False)
复杂度 O ( ∣ s ∣ × ∣ t ∣ ) O(\vert s \vert \times \vert t \vert) O(∣s∣×∣t∣)与暴力法没本质区别
- 当
flag
为True
时,表示当前调用是在查找subRoot
是否是root
的子树 - 当
flag
为False
时,表示当前调用是在检查两棵树是否完全相同
好丑陋的代码,就为了用一个函数实现递归,毫无必要
题5:二叉树的最大深度
在层序遍历中做过这个题二叉树的最大深度,一层层遍历计数,此处不用层序遍历再做一遍
- 深度:深度是指从树的根节点到某个节点的路径长度(即经过的边的数量),深度是从上往下计算的(根左右,前序遍历)
- 高度:高度是指从某个节点到它的最远叶子节点的路径长度,高度是从下往上计算的(左右根 后序遍历)
不过根节点的高度就是二叉树的最大深度,所以用前序遍历和后序遍历解这道题都可以
前序遍历(标准的深度求法)
根左右,每次深度都加1,但是当前我在遍历根的时候我不知道深度是多少,这个要交给后续的遍历左子树右子树去算,所以在函数里面传递深度这个参数,返回值也是深度
- 参数和返回值:传递当前根节点指针和当前深度
- 边界条件:空节点,直接返回深度(深度到空节点就不在增加)
- 每层处理逻辑:当前节点不为空,深度+1,求左子树的深度,求右子树的深度,取它们的最长深度,返回
class Solution:
def maxDepth(self, root: Optional[TreeNode], depth = 0) -> int:
if root == None:
return depth
return max(self.maxDepth(root.left,depth + 1), self.maxDepth(root.right,depth+1))
后序遍历
左右根,根节点的高度是左右节点+1
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root == None:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
- 参数和返回值:传递当前根节点指针
- 边界条件:空节点,直接返回0,空节点的高度为0
- 每层处理逻辑:求左子树的高度,求右子树的高度,它们的高度+1就是当前根节点的高度
n叉树的最大深度
class Solution:
def maxDepth(self, root: 'Node') -> int:
if root == None:
return 0
max = 0
n = len(root.children) - 1
while n >= 0:
tmp = self.maxDepth(root.children[n])
if tmp > max:
max = tmp
n -= 1
return max + 1
题6:二叉树的最小深度
在层序遍历中做过这个题二叉树的最小深度,一层层遍历计数,碰到叶子节点直接返回,此处不用层序遍历再做一遍
最小深度:根节点到叶子节点的最短距离
后序遍历
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if root == None:
return 0
if root.right == None:
return self.minDepth(root.left) + 1
if root.left == None:
return self.minDepth(root.right) + 1
return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
- 参数和返回值:传递当前根节点指针
- 边界条件:空节点,直接返回0,空节点的高度为0
- 每层处理逻辑:若右子树不存在,求左子树的高度,若左子树不存在,求右子树最小高度,若左右子树都存在,两者中最小高度更小的加一就是当前根节点的高度
避免当遇到不存在的节点时返回的高度小于另外一侧的高度,造成误判,我们要找的是叶子节点而不是空节点
前序遍历
class Solution:
def minDepth(self, root: Optional[TreeNode],depth:int = 0) -> int:
if root == None:
return depth
if root.right == None:
return self.minDepth(root.left, depth + 1)
if root.left == None:
return self.minDepth(root.right, depth + 1)
return min(self.minDepth(root.left, depth + 1), self.minDepth(root.right, depth + 1))
- 参数和返回值:传递当前根节点指针和当前深度
- 边界条件:空节点,直接返回深度(深度到空节点就不在增加)
- 每层处理逻辑:当前节点不为空,深度+1,若右子树不存在,求左子树的深度,若左子树不存在,求右子树最小深度,若左右子树都存在,两者中最小深度更小的,返回
TBC
(未完待续)