LeetCode题目——二叉树篇
前言
二叉树是数据结构的基础知识,应用的非常广,不止在数据结构中使用的非常广泛,在算法中也会经常用到这样的思想。
关于二叉树的题目,大多会用到递归,递归其实就是前中后序遍历,除了这三种遍历方式外,层次遍历也是处理相关问题的一大利器。
做二叉树相关题目时,也要注意和栈、队列等其他知识结合起来,这样才可以更好的做出解答。比如,可以使用递归解决的问题,一定可以通过栈来实现,这是由递归的底层逻辑决定的。
本人水平有限,以下解答若有问题,欢迎评论或者私信。
一、二叉树的前、中、后序遍历
二叉树的这三种遍历方式,其实就是根据根节点所在的位置来决定的。
递归遍历比较简单,所以只给出代码。
1.前序遍历
递归实现
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 保存结果
result = []
def traversal(root: TreeNode):
if root == None:
return
result.append(root.val) # 前序
traversal(root.left) # 左
traversal(root.right) # 右
traversal(root)
return result
迭代法
上面也说了,只要是递归的问题,用栈都可以解决
前序遍历本质上就是先对根节点处理,然后再输出左孩子右孩子,但是栈有一个特性,即先进后出,所以我们应该先让根节点的右孩子进栈。
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
stack = [root]
res = []
while stack:
node = stack.pop()
res.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return res
2.中序遍历
递归
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
def traversal(root: TreeNode):
if root == None:
return
traversal(root.left) # 左
result.append(root.val) # 中序
traversal(root.right) # 右
traversal(root)
return result
迭代法
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = [] #定义的返回数组
stack = []
cur = root #从头结点开始处理
while cur or stack:
while cur: #当节点不为空的时候,就进栈,并继续向左,向下寻找
stack.append(cur)
cur = cur.left
if stack: #当已经到达最下最左的节点时,出栈,并使用res记录数值
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
3.后序遍历
递归
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
def traversal(root: TreeNode):
if root == None:
return
traversal(root.left) # 左
traversal(root.right) # 右
result.append(root.val) # 后序
traversal(root)
return result
迭代
前序序列的左右子树互换,就可得到逆后序序列,这个技巧是在备考期间看到的,感觉是最好理解的一种。前序序列的左右子树交换无非就是修改前序序列的进栈顺序,让左子树先进栈,右子树后进即可。最后再用一个栈把逆后序序列逆置,就可得到后续序列。
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root is None:
return []
res = []
stack1 = []
stack2 = []
stack1.append(root)
while stack1:
cur1 = stack1.pop()
stack2.append(cur1)
if cur1.left:
stack1.append(cur1.left)
if cur1.right:
stack1.append(cur1.right)
while stack2:
cur2 = stack2.pop()
res.append(cur2.val)
return res
二、二叉树的层次遍历
顾名思义,层次遍历就是对二叉树一层一层的遍历,相当于广度优先搜索(BFS)。层次遍历使用队列来实现。
以下代码可以作为层次遍历的模板,
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
result = [] #存放最后的结果
if not root:
return result
from collections import deque #双端队列
que = deque([root])
while que:
res=[] #存放每一层的元素
for _ in range(len(que)): #遍历处理每一层的节点
cur = que.popleft()
res.append(cur.val)
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
result.append(res[:])
return result
三、二叉树的右视图
'''
这道题目本质上就是将每一层最右侧的节点的数值输出
'''
class Solution:
def rightSideView(self, root: TreeNode) -> List[int]:
if root is None:
return []
result = [] #返回结果
from collections import deque
que = deque([root])
while que:
res = [] #存放每一层的结果
for _ in range(len(que)):
cur = que.popleft()
res.append(cur.val)
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
result.append(res[-1])
return result
四、二叉树的层平均值
'''
增加一个计数器
'''
class Solution:
def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
if root is None:
return []
result = []
from collections import deque
que = deque([root])
while que:
sum,count = 0,0
for _ in range(len(que)):
cur = que.popleft()
sum += cur.val
count += 1
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
avg = sum/count
result.append(avg)
return result
五、N叉树的层序遍历
注意python双端队列中,extend和append的差别。python中的双端队列是比较常用的一个队列。
'''
extend:
A = [1,2,3,4]
A.extend([5,6])
A
[1,2,3,4,5,6]
append:
A = [1,2,3,4]
A.append([5,6])
A
[1,2,3,4,[5,6]]
'''
class Solution:
def levelOrder(self, root: 'Node') -> List[List[int]]:
if not root:
return []
result = []
from collections import deque
que = deque([root])
while que:
res = []
for _ in range(len(que)):
cur = que.popleft()
res.append(cur.val)
que.extend(cur.children) #若孩子节点存在,则入队列
result.append(res)
return result
六、在每个树行中找最大值
使用层次遍历模板
# 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 largestValues(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
result = [] #定义一个结果数组
from collections import deque #层序遍历队列
que = deque([root])
while que:
ans = -float('inf') #记录每一层的最大值
for _ in range(len(que)): #对每一层进行处理
cur = que.popleft()
ans = max(ans,cur.val)
if cur.left: #左孩子存在,则左孩子入队
que.append(cur.left)
if cur.right: #右孩子存在,则右孩子入队
que.append(cur.right)
result.append(ans) #将每一层的最大值加入result数组中
return result
七、填充每个节点的下一个右侧节点指针(完全二叉树)
"""
# Definition for a Node.
class Node:
def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
self.val = val
self.left = left
self.right = right
self.next = next
"""
class Solution:
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
if not root:
return root
#从根节点开始遍历,leftmost为每一层最左侧的节点
leftmost = root
#遍历一层的节点
while leftmost.left:
#head可以理解为每一层的头结点
head = leftmost
while head:
#同一个head父节点的情况下
head.left.next = head.right
#head和head的兄弟节点的情况下
if head.next:
head.right.next = head.next.left
head = head.next
#寻找下一层最左侧的节点
leftmost = leftmost.left
return root
八、填充每个节点的下一个右侧节点指针(普通二叉树)
层次遍历
"""
# Definition for a Node.
class Node:
def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
self.val = val
self.left = left
self.right = right
self.next = next
"""
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return None
que = collections.deque([root])
#对每一层进行遍历
while que:
#每一层都新建一个节点,用于连接左右节点
tail = None
#遍历当前层
for _ in range(len(que)):
cur = que.popleft()
#让尾结点指向当前节点
if tail:
tail.next = cur
tail = cur
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
return root
九、完全二叉树的节点个数
# 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 countNodes(self, root: TreeNode) -> int:
return self.getNodesNum_level(root)
'''递归方法实现'''
def getNodesNum_re(self,cur):
if not cur:
return 0
return self.countNodes(cur.left)+self.countNodes(cur.right)+1
'''层次遍历'''
def getNodesNum_level(self,cur):
if not cur:
return 0
que = collections.deque([cur])
count = 0
while que:
for _ in range(len(que)):
node = que.popleft()
count += 1
if node.left:
que.append(node.left)
if node.right:
que.append(node.right)
return count
以上题目基本上就是通过层次遍历的模板来进行解答,层次遍历应用的非常广,需要多做题来进行巩固
十、对称二叉树
递归方法
# 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
'''
必用递归
轴对称的意思是root左子树与root右子树比较,数值也要一样
'''
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if root is None:
return True
return self.compare(root.left,root.right)
def compare(self,left,right):
#首先写出显然的情况
if left == None and right != None: return False
elif left != None and right == None: return False
elif left == None and right == 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
双端队列
# 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
'''
采用python中的双端队列
'''
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
#创建一个双端队列
que = collections.deque()
que.append(root.left)
que.append(root.right)
while que:
leftcur = que.popleft()
rightcur = que.popleft()
if not leftcur and not rightcur:
continue
if not leftcur or not rightcur or leftcur.val != rightcur.val:
return False
que.append(leftcur.left)
que.append(rightcur.right)
que.append(leftcur.right)
que.append(rightcur.left)
return True
十一、二叉树的直径
这道题虽然是简单题目,为啥我觉得和困难的差不多,用光了我所有的脑细胞。
该题说求出二叉树的直径,并不是一定会经过根节点,第一想法就是用递归,求出左子树的最大高度和右子树的最大高度,然后相加加一,信心满满的提交,果然是错误的。下面举个例子就可以明白。最长的路径不是蓝色的那条线,而是红色的那条。需要一个值来保存我这个每次比较更新的最大直径值。在每次获得一个节点的左子树和右子树的值的时候,都需要比较一下self.res和左子树高度+右子树高度的大小,把更大的保存下来。
'''
递归
'''
class Solution:
def __init__(self):
self.res = 0
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.maxDep(root)
return self.res
def maxDep(self,cur):
width = 0
if cur is None:
return 0
left = self.maxDep(cur.left)
right = self.maxDep(cur.right)
self.res = max(self.res,left+right) #每个节点都要去判断最大值与左子树的高度和右子树的高度的和谁最大
return max(left,right)+1
这个题真是为难死我了
十二、合并二叉树
递归实现完全没问题
# 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 mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
#先将显然的情况写出
if root1 is None and root2 is None:
return None
elif root1 != None and root2 == None:
return root1
elif root1 == None and root2 != None:
return root2
root1.val = root1.val + root2.val
self.mergeTrees(root1.left,root2.left)
self.mergeTrees(root1.right,root2.right)
return root1
十三、二叉树的所有路径
对于这种返回路径的题目,如果是返回一条,可用一个数组来返回,但是如果返回多条,就需要定义两个数组,以此题为例,需要一个数组记录一条路径,还需要一个数组返回所有的路径。
# 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 binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
path = '' #存放单条路径
res = [] #存放输出结果
def traversal(root,path,res):
if root == None:
return []
path += str(root.val)
if root.left is None and root.right is None:
res.append(path)
if root.left:
traversal(root.left,path + '->',res)
if root.right:
traversal(root.right,path+'->',res)
traversal(root,path,res)
return res
十四、左叶子之和
# 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 sumOfLeftLeaves(self, root: TreeNode) -> int:
'''
递归,能用递归解决的,用栈也可以
if root is None:
return 0
sum = 0
if root.left != None and root.left.left==None and root.left.right==None:
sum += root.left.val
sumLeft = self.sumOfLeftLeaves(root.left)
sumRight = self.sumOfLeftLeaves(root.right)
return sum+sumLeft+sumRight
'''
if root == None:
return 0
stack = []
sum = 0
stack.append(root)
while stack:
cur = stack.pop()
if cur.left and cur.left.left==None and cur.left.right==None:
sum += cur.left.val
if cur.left:
stack.append(cur.left)
if cur.right:
stack.append(cur.right)
return sum
十五、二叉搜索树中的搜索
二叉搜索树其实就类似于折半查找
二叉搜索树的特点是,中序遍历序列为递增的序列
# 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 searchBST(self, root: TreeNode, val: int) -> TreeNode:
if root is None:
return root
if root.val == val:
return root
elif root.val < val:
return self.searchBST(root.right,val)
else:
return self.searchBST(root.left,val)
return None
十六、路径总和
第一想法是定义一个变量sum来记录当前的路径和,但是发现并不是特别好用,然后就想到了递减,当targetSum=0,并且到达了叶子节点,就返回True,可以采用递归的方式。
# 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 hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False
targetSum -= root.val
if root.left is None and root.right is None and targetSum==0:
return True
return self.hasPathSum(root.left,targetSum) or self.hasPathSum(root.right,targetSum)
十七、路径总和 ||
像我前面说的,这个题目就需要返回多条路径,所以应该设两个数组。
使用回溯的思想,其实树的深度优先遍历(DFS)就是回溯。
# 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 pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
path = [] #存放符合条件的单一路径
res = [] #返回所有符合条件的路径,即所有的path
if root is None:
return None
def findPath(root,targetSum,res):
if root.left is None and root.right is None and targetSum==0:
res.append(path[:])
return
#回溯法,先向最深的地方搜索
if root.left:
path.append(root.left.val)
targetSum -= root.left.val
findPath(root.left,targetSum,res)
path.pop()
targetSum += root.left.val
if root.right:
path.append(root.right.val)
targetSum -= root.right.val
findPath(root.right,targetSum,res)
path.pop()
targetSum += root.right.val
path.append(root.val)
findPath(root,targetSum-root.val,res) #很多人可能不清楚为什么要减去root.val,因为我们是从根节点开始的,只需要再判断左右子树就行了
return res
十八、从中序与后序遍历序列构造二叉树
给出序列构造二叉树,都是有规律的,后序序列的最后一个一定为根,然后在中序序列中找到这个数,这个数的左边就是根节点的左子树,右面就是根节点的右子树。递归着继续分析就可以得出。
# 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 buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
if not postorder:
return None
root = TreeNode(postorder[-1])#创建树
n = inorder.index(root.val)
root.left = self.buildTree(inorder[:n],postorder[:n])
root.right = self.buildTree(inorder[n+1:],postorder[n:-1])
return root
十九、从前序与中序遍历序列构造二叉树
原理和上题一样,前序序列的第一个为根节点,然后和上题同理。
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder or not inorder:
return None
root = TreeNode(preorder[0])#创建树
n = inorder.index(root.val)
root.left = self.buildTree(preorder[1:n+1],inorder[:n])
root.right = self.buildTree(preorder[n+1:],inorder[n+1:])
return root
二十、二叉搜索树中的众数
使用字典来解答这个问题,出现几次dict字典中的下标就加一,最后可以得到出现的最多的。
# 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 findMode(self, root: TreeNode) -> List[int]:
if root is None:
return []
dicts = {}
que = collections.deque()
que.append(root)
while que:
cur_val = que.popleft()
if cur_val.val not in dicts:
dicts[cur_val.val] = 1
else:
dicts[cur_val.val] += 1
if cur_val.left:
que.append(cur_val.left)
if cur_val.right:
que.append(cur_val.right)
maxVal = max(dicts.values())
res = []
for item,val in dicts.items():
if val>=maxVal:
res.append(item)
return res
二十一、二叉树的最近公共祖先
这个题有点难度的,需要手动实现一下才好理解。
当root=p or root=q or root=None时,就直接返回root,因为如果root是前两种情况,则它一定是公共祖先。然后递归的寻找左子树和右子树。
当left和right都为空的时候,就返回None,即返回其中的任意一个都行。
left为空,right不为空,则返回right
left不为空,right为空,则返回left
如果left和right都不为空,则返回root即可。
这里一定要自己手动实现,否则很难理解
# 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:
return None
if root==p or root==q or root==None:
return root
left = self.lowestCommonAncestor(root.left,p,q)
right = self.lowestCommonAncestor(root.right,p,q)
if left==None and right==None: return right
if left==None and right!=None: return right
if left!= None and right==None: return left
if left and right:
return root
二十二、二叉搜索树的最近公共祖先
在二叉搜索树中,两个节点的最近公共祖先的值一定在这两个节点之间,所以可以通过值的比较来寻找
如果根节点的值比p和q都大则应该在左子树中寻找,都小的话则应该在右子树中寻找。若在之间,则直接返回root。
# 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:
return root
if root.val>p.val and root.val>q.val:
return self.lowestCommonAncestor(root.left,p,q)
if root.val<p.val and root.val<q.val:
return self.lowestCommonAncestor(root.right,p,q)
return root
二十三、删除二叉搜索树中的节点
# 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
'''
在删除一个节点时,会有五种情况:
1.未找到节点,则直接返回
2.应删除节点node的左子树为空,则需要把node的右子树接上
3.应删除节点node的右子树为空,则需要把node的左子树接上
4.应删除节点node的左、右子树都不为空,把node的左子树接到node的右子树的最左下的节点
5.应删除的节点为叶子节点,直接删除即可
'''
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root is None:
return root
if root.val==key:
if not root.left and not root.right:
del root
return None
if root.left==None and root.right!=None:
tmp = root
root = root.right
del tmp
return root
if root.left!=None and root.right==None:
tmp = root
root = root.left
del tmp
return root
if root.left!=None and root.right!=None:
node = root.right
while node.left:
node = node.left
node.left = root.left
tmp = root
root = root.right
del tmp
return root
if root.val<key:
root.right = self.deleteNode(root.right,key)
if root.val>key:
root.left = self.deleteNode(root.left,key)
return root