Leetcode刷题记录——二叉树篇
二叉树的基本理论
两种形式
● 满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
● 完全二叉树
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
二叉搜索树
二叉搜索树是一个有序树
● 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
● 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
● 它的左、右子树也分别为二叉排序树
搜索的时间复杂度是logn级别
下面的两棵树都是搜索树
平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
⚠️注意:
● C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn
● unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
二叉树的存储方式
两种方式
链式存储(LinkedList实现)
链式存储是通过指针把分布在各个地址的节点串联在一起。
顺序存储(数组实现)
顺序存储在内存中是连续分布的。
如果父节点的数组下标是i,那么左孩子是2i+1,右孩子是2i+2
二叉树的遍历方式
其实一共有四小类,两大类。
● DFS(深度优先搜索)
前序遍历(中左右)
中序遍历(左中右)
后序遍历(左右中)
看下面的例子
● BFS(广度优先搜索)
层序遍历
二叉树递归遍历的实现
递归三要素
● 确定递归函数的参数和返回值
● 确定终止条件(防止栈溢出错误,导致OS强制退出程序)
● 确定单层递归的逻辑
- 前序遍历
# Define a binary tree node
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
res.append(node.val)
dfs(node.left)
dfs(node.right)
return res
- 中序遍历
# Define a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def inorderTraversal(self,root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
dfs(node.left)
res.append(node.val)
dfs(node.right)
return res
- 后序遍历
# Define a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
dfs(node.left)
dfs(node.right)
res.append(node.val)
return res
二叉树的迭代遍历
- 前序遍历
#Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack = [root]
result = []
if not root:
return result
while stack:
node = stack.pop()
result.append(node.val)
# 右孩子入栈
if node.right:
stack.append(node.right)
# 左孩子入栈
if node.left:
stack.append(node.left)
return result
- 中序遍历
# Define a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [] # 不能提前将root结点加入stack中
result = []
cur = root
while cur or stack:
# 先迭代访问最底层的左子树结点
if cur:
stack.append(cur)
cur = cur.left
# 到达最左结点后处理栈顶结点
else:
cur = stack.pop()
result.append(cur.val)
# 取栈顶元素右结点
cur = cur.right
return result
- 后序遍历
#Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack = [root]
result = []
if not root:
return []
while stack:
node = stack.pop()
result.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
result = result[::-1]
return result
二叉树统一遍历方法
前面的通过使用栈实现二叉树的迭代遍历,会发现前序和后序遍历稍微修改一下就可以,但是中序遍历差距很大,为此,想到一种统一的遍历方法。
为什么前面的迭代方法中序和前序、后序差距很大?因为栈无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。但是我们将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,这样就可以解决了。具体的标记方法是:要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
- 前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st= []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
if node.right: #右
st.append(node.right)
if node.left: #左
st.append(node.left)
st.append(node) #中
st.append(None)
else:
node = st.pop()
result.append(node.val)
return result
- 中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st = []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
if node.right: #添加右节点(空节点不入栈)
st.append(node.right)
st.append(node) #添加中节点
st.append(None) #中节点访问过,但是还没有处理,加入空节点做为标记。
if node.left: #添加左节点(空节点不入栈)
st.append(node.left)
else: #只有遇到空节点的时候,才将下一个节点放进结果集
node = st.pop() #重新取出栈中元素
result.append(node.val) #加入到结果集
return result
- 后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st = []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
st.append(node) #中
st.append(None)
if node.right: #右
st.append(node.right)
if node.left: #左
st.append(node.left)
else:
node = st.pop()
result.append(node.val)
return result
二叉树的搜索
二叉树的广搜(BFS)
对于广度优先搜索,需要采用层序遍历的方式。对于递归遍历可以使用栈,但是对于层序遍历使用的是队列。
代码实现
方法一(但是没法判断某个节点是哪一层)
from collections import deque
# Difine a binary tree
class TreeNode:
def __init__(self,val,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 not root:
return []
queue = deque()
result = []
# python队列实现使用append和popleft即可
# 根节点入队
queue.append(root)
# 后面进行循环遍历
while queue:
node = queue.popleft()
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
方法二(判断某节点属于哪一层,每一层的单独处理)
from collections import deque
# Difine a binary tree
class TreeNode:
def __init__(self,val,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 not root:
return []
queue = deque()
result = []
# 根节点入队
queue.append(root)
while queue:
level = []
for i in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
注意事项
这里有个技巧,就是层序遍历时候对于同一层节点的处理,可以使用一个队列,对于队列的长度进行循环,就可以得到本层的节点。
二叉树的题目实战(Leetcode高频题)
反转二叉树(Leetcode226)
题目要求
思路
可以使用二叉树遍历的方式,可通过迭代法或者递归实现。只需要将某节点的左右孩子交换即可。
代码
方法1 前序遍历
# Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
# Solution
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None
# 前序遍历
node = root
# 中
node.left , node.right = node.right, node.left
# 左
self.invertTree(node.left)
# 右
self.invertTree(node.right)
return root
方法2 后序遍历
# Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
# Solution
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None
node = root
# 后序遍历顺序:左右中
# 左
self.invertTree(node.left)
# 右
self.invertTree(node.right)
# 中
node.left , node.right = node.right , node.left
return root
⚠️注意事项
● 在写这个题目的时候,一定注意输入的数据类型,它并不是一个列表输入列表输出,因此在交换左右孩子的过程中已经完成了二叉树的翻转。
● ⚠️注意:在递归的过程中一定不能漏了self,否则会报错。
对称二叉树(Leetcode101)
题目要求
思路
可以发现,对于对称二叉树的判断,其实只需要判断两棵树(左子树和右子树)。由于我们要比较两棵子树的外侧和内侧是否相等。但是要注意,只能用后序遍历(其实是广义的),一颗子树左右中,另一颗子树右左中。
代码
方法1(递归单独写,但是此时需要self连接):
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def compare(self,left,right):
if left is None and right is not None:
return False
elif left is not None and right is None:
return False
elif left is None and right is None:
return True
elif left.val != right.val:
return False
# 左子树:左右中;右子树:右左中;在中的位置判断相等
outside = self.compare(left.left,right.right)
inside = self.compare(left.right,right.left)
return outside and inside
def isSymmetric(self, root):
# 注意,一定需要先看左节点和右节点的问题,因为需要考虑空指针的事情
leftTree = root.left
rightTreee = root.right
return self.compare(leftTree,rightTreee)
方法2(不使用self关键字,直接写函数体里面):
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def isSymmetric(self, root):
# 注意,一定需要先看左节点和右节点的问题,因为需要考虑空指针的事情
leftTree = root.left
rightTreee = root.right
def compare(left,right):
if left is None and right is not None:
return False
elif left is not None and right is None:
return False
elif left is None and right is None:
return True
elif left.val != right.val:
return False
# 左子树:左右中;右子树:右左中;在中的位置判断相等
outside = compare(left.left,right.right)
inside = compare(left.right,right.left)
return outside and inside
return compare(leftTree,rightTreee)
⚠️注意事项
这里使用了递归,但是要和二叉树的递归遍历区别开来,递归函数单独写,然后在直接调用递归函数,入口参数直接传左子树和右子树即可。
二叉树的最大深度(Leetcode104)
题目要求
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
思路(可以使用前序遍历或者后序遍历)
根节点的高度就是二叉树的最大深度,通过后序遍历求解二叉树最大深度。
代码
方法1:后序遍历
# Define a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
# Define a Solution
class Solution:
def getDepth(self,node):
if node is None:
return 0
leftDepth = self.getDepth(node.left)
rightDepth = self.getDepth(node.right)
Depth = max(leftDepth,rightDepth) + 1
return Depth
def maxDepth(self, root):
depth = self.getDepth(root)
return depth
方法2:前序遍历
⚠️注意事项
本题比较简单,但是需要注意一下递归函数的使用,self关键字不要忘了加,否则会报错。
二叉树的最小深度(Leetcode111)
题目要求
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最小深度 2。
思路
可以采用后序遍历或者前序遍历,后序遍历,求的是根节点到叶子节点最小距离。
代码
方法1:后序遍历
#Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
# Difine a Solution for this problem
class Solution:
def getminDepth(self,node):
if node is None:
return 0
leftDepth = self.getminDepth(node.left)
rightDepth = self.getminDepth(node.right)
# 需要单独判断左右子树,这个题目很刁钻
if node.left is None and node.right is not None:
minDepth = rightDepth + 1
elif node.left is not None and node.right is None:
minDepth = leftDepth + 1
else:
minDepth = min(leftDepth,rightDepth) + 1
return minDepth
def minDepth(self, root):
TreeminDepth = self.getminDepth(root)
return TreeminDepth
⚠️注意事项
这个题目很刁钻,写递归函数的时候,终止条件需要确定好,其次,需要判断左右子树是否为空的情况,因为这个题目定义了二叉树的最小深度为离根节点最近的叶子节点的距离。
完全二叉树的节点个数(Leetcode222)
题目要求
给出一个完全二叉树,求出该树的节点个数。
示例 1:
● 输入:root = [1,2,3,4,5,6]
● 输出:6
示例 2:
● 输入:root = []
● 输出:0
示例 3:
● 输入:root = [1]
● 输出:1
提示:
● 树中节点的数目范围是[0, 5 * 10^4]
● 0 <= Node.val <= 5 * 10^4
● 题目数据保证输入的树是 完全二叉树
思路
这个题目是完全二叉树,因此可以用完全二叉树的性质简化求解,但是先给出最笨的方法,普通二叉树的求法。
代码
方法1:最笨方法(后序遍历)
# Difine a binary tree
class TreeNode:
def __init__(self,val,left = None,right = None):
self.val = val
self.left = left
self.right = right
# BackOrderedTraverse
class Solution:
def getNodeNum(self,node):
if node is None:
return 0
leftNodeNum = self.getNodeNum(node.left)
rightNodeNum = self.getNodeNum(node.right)
NodeNum_Total = leftNodeNum + rightNodeNum + 1
return NodeNum_Total
def countNodes(self, root: Optional):
TotalNodeNum = self.getNodeNum(root)
return TotalNodeNum