1.二叉树
文章目录
写在前面
本系列笔记主要作为笔者刷题的题解,所用的语言为Python3
,若于您有助,不胜荣幸。
1.16从中序遍历和后序遍历构造二叉树
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
思路:处理的步骤是我们首先确定中间节点,即根节点,然后利用中间节点去不断切割中序遍历和后序遍历,迭代执行这一过程最后构造出完整的二叉树。
- 后序数组为0,空节点
- 后序数组最后一个元素为节点元素
- 寻找中序数组位置作为切割点
- 切中序数组
- 切后序数组
- 递归处理左区间和右区间
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if len(postorder) == 0 or len(inorder) == 0: # 如果传入的非法则返回空
return None
rootvalue: int = postorder[-1]
root: TreeNode = TreeNode(rootvalue)
if len(postorder) == 1: # 剪枝(可省略)
return root
# 切割中序
leftinorder: List[int] = inorder[:inorder.index(rootvalue)]
rightinorder: List[int] = inorder[inorder.index(rootvalue)+1:]
# 切割后序,中序数组大小一定和后序数组大小是相同的,所以可以用中序的大小长度来切割
leftpostorder: List[int] = postorder[:len(leftinorder)]
rightpostorder: List[int] = postorder[len(leftinorder): -1]
root.left = self.buildTree(leftinorder, leftpostorder)
root.right = self.buildTree(rightinorder, rightpostorder)
return root
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not preorder:
return None
rootval: int = preorder[0]
root: TreeNode = TreeNode(rootval)
if len(preorder) == 1:
return root
idx: int= inorder.index(rootval)
leftinorder: List[int] = inorder[:idx]
rightinorder: List[int] = inorder[idx+1:]
leftpreorder: List[int] = preorder[1:1+len(leftinorder)]
rightpreorder: List[int] = preorder[1+len(leftinorder):]
root.left = self.buildTree(leftpreorder, leftinorder)
root.right = self.buildTree(rightpreorder, rightinorder)
return root
1.17最大二叉树
给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums
构建的 最大二叉树 。
思路:凡是进行构造二叉树的题目,我们都应该使用前序遍历的方式即中左右
,这样我们就能先构造当前的节点中
,然后再构造另外两个左右子树。这道题和利用中序遍历和后序遍历数组来构造二叉树的题目比较类似,也是需要对数字进行分隔,在对数组进行分隔的时候,我们需要关注下标始终满足左闭右开的一个规则[)
。
解法一:给出遇到空数组的终止条件
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
if not nums: # 遇到空则构造None
return None
if len(nums) == 1: # 终止条件
return TreeNode(nums[0])
cur_val: int = max(nums)
cur_idx: int = nums.index(cur_val)
root: TreeNode = TreeNode(cur_val) # 中
# 切割区间
left: List[int] = nums[:cur_idx]
right: List[int] = nums[cur_idx+1:]
root.left = self.constructMaximumBinaryTree(left) # 左
root.right = self.constructMaximumBinaryTree(right) # 右
return root
解法二:不给出遇到空数组的终止条件,但要确保数组至少包含一个元素
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
if len(nums) == 1: # 终止条件
return TreeNode(nums[0])
cur_val: int = max(nums)
cur_idx: int = nums.index(cur_val)
root: TreeNode = TreeNode(cur_val) # 中
# 切割区间
if cur_idx > 0: # 保证区间内至少包含一个元素
left: List[int] = nums[:cur_idx]
root.left = self.constructMaximumBinaryTree(left) # 左
if cur_idx < len(nums) - 1:
right: List[int] = nums[cur_idx+1:]
root.right = self.constructMaximumBinaryTree(right) # 右
return root
一般情况来说,如果让空节点进入递归,就不加if
的条件判断,如果不让空节点进入递归,就加上if
的条件判断来进行限制,终止条件也需要相应地调整。
解法三:使用下标,不切割nums
class Solution:
def traversal(self, nums: List[int], left: int, right: int) -> Optional[TreeNode]:
if left >= right:
return None
max_val_idx: int = left
for i in range(left+1, right):
if nums[i] > nums[max_val_idx]:
max_val_idx = i
root: TreeNode = TreeNode(nums[max_val_idx])
root.left = self.traversal(nums, left, max_val_idx)
root.right = self.traversal(nums, max_val_idx + 1, right)
return root
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
return self.traversal(nums, 0, len(nums))
1.18合并二叉树
给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
思路:这道题需要我们同时操作两个二叉树的根节点,即root1
和root2
,我们只需要对这两棵同步进行遍历即可,什么是同步遍历呢?就是采用相同的遍历顺序,当其中一棵树遇到空节点的时候直接采用另一棵树的节点值作为新的节点即可,如果两棵树都不是空节点,则直接返回其相加和的值即可。
解法一:递归
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if root1 == None: # 终止条件
return root2
if root2 == None:
return root1
root1.val += root2.val # 中,合并值
root1.left = self.mergeTrees(root1.left, root2.left) # 左,同步遍历
root1.right = self.mergeTrees(root1.right, root2.right) # 右,同步遍历
return root1
解法二:迭代,类似层序遍历
from collections import deque
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if not root1:
return root2
if not root2:
return root1
que: deque = deque()
que.append((root1, root2))
while que:
node1, node2 = que.popleft()
node1.val += node2.val
if node1.left and node2.left:
que.append((node1.left, node2.left))
elif not node1.left:
node1.left = node2.left
if node1.right and node2.right:
que.append((node1.right, node2.right))
elif not node1.right:
node1.right = node2.right
return root1
1.19二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点 root
和一个整数值 val
。
你需要在 BST 中找到节点值等于 val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null
。
思路:二叉搜索树,需要满足的一个条件是,当前根节点的值要大于左子树的值,并且小于右子树的值,符合这种条件的树,我们可以称之为二叉搜索树。遇到二叉搜索树,我们一般采用中序遍历。
解法一:递归法
class Solution:
def searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 避免传入空指针
return None
if root.val == val: # 终止条件
return root
elif root.val > val: # 左
return self.searchBST(root.left, val)
elif root.val < val: # 右
return self.searchBST(root.right, val)
解法二:迭代法
class Solution:
def searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 避免传入空指针
return None
stack: List[Optional[TreeNode]] = []
stack.append(root)
while stack:
cur: Optional[TreeNode] = stack.pop()
if not cur:
return None
if cur.val == val: # 中
return cur
elif cur.val > val:
stack.append(cur.left) # 左
elif cur.val < val:
stack.append(cur.right) # 右
class Solution:
def searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
while root:
if root.val > val: root = root.left
elif root.val < val: root = root.right
else: return root
return None
1.20验证二叉搜索树
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
思路:传入空指针可以被视为一种二叉搜索树,也是完全二叉树,也是完全二叉搜索树,我们可以先使用一个数组来保存二叉搜索树的中序遍历结果左中右
,如果数组是有序的,那么就说明该二叉搜索树是有序的,此外我们还可以使用递归的方法来进行处理。
解法一:递归
class Solution:
def __init__(self):
self.max_val: float = float('-inf')
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left: bool = self.isValidBST(root.left)
# 中序遍历,验证遍历的元素是不是从小到大
if root.val > self.max_val:
self.max_val = root.val
else:
return False
right: bool = self.isValidBST(root.right)
return left and right
class Solution:
def __init__(self):
self.pre_node: Optional[TreeNode] = None
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left: bool = self.isValidBST(root.left)
# 中序遍历,验证遍历的元素是不是从小到大
if self.pre_node and self.pre_node.val >= root.val:
return False
# 记录前一个节点
self.pre_node = root
right: bool = self.isValidBST(root.right)
return left and right
解法二:迭代
统一迭代法的中序遍历
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
sorted_val: List[int] = []
stack: List[Optional[TreeNode]] = []
stack.append(root)
while stack:
cur: TreeNode = stack.pop()
if cur != None:
if cur.right != None:
stack.append(cur.right) # 右
stack.append(cur) # 中
stack.append(None)
if cur.left != None:
stack.append(cur.left) # 左
else:
cur = stack.pop()
sorted_val.append(cur.val)
for i in range(1, len(sorted_val)):
if sorted_val[i] <= sorted_val[i-1]:
return False
return True
递归迭代法中序遍历
class Solution:
def __init__(self):
self.sorted_val: List[int] = []
def traversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return
self.traversal(root.left)
self.sorted_val.append(root.val)
self.traversal(root.right)
def isValidBST(self, root: Optional[TreeNode]) -> bool:
self.traversal(root)
if not self.sorted_val:
return True
for i in range(1, len(self.sorted_val)):
if self.sorted_val[i] <= self.sorted_val[i-1]:
return False
return True
1.21二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
解法一:递归
class Solution:
def __init__(self):
self.result: int = float('inf')
self.pre: Optional[TreeNode] = None
def traversal(self, cur: Optional[TreeNode]) -> None:
if not cur:
return
self.traversal(cur.left) # 左
if self.pre != None:
self.result = min(self.result, abs(cur.val - self.pre.val)) # 中
self.pre = cur
self.traversal(cur.right) # 右
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
if not root: # 避免传入空指针
return -1
self.traversal(root)
return self.result
解法二:递归,然后查找
class Solution:
def __init__(self):
self.result: int = float('inf')
self.vals: List[int] = []
def traversal(self, cur: Optional[TreeNode]) -> None:
if not cur:
return
self.traversal(cur.left)
self.vals.append(cur.val) # 将二叉搜索树转换为有序数组,中序遍历
self.traversal(cur.right)
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
self.traversal(root)
for i in range(1, len(self.vals)):
self.result = min(self.result, abs(self.vals[i] - self.vals[i-1]))
return self.result
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
这两道题是一样的题目,可以同时解决。
1.22二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
解法一:先递归得到一个map,然后从这个map中查找最大的key
class Solution:
def __init__(self):
self.num_map: dict = {}
def traversal(self, cur: Optional[TreeNode]) -> None:
if not cur:
return
self.traversal(cur.left) # 左
self.num_map[cur.val] = self.num_map.get(cur.val, 0) + 1 # 中
self.traversal(cur.right) # 右
def findMode(self, root: Optional[TreeNode]) -> List[int]:
self.traversal(root)
max_val: int = max(self.num_map.values())
res: List[int] = [key for key, value in self.num_map.items() if value == max_val]
return res
解法二:双指针法递归
class Solution:
def __init__(self):
self.result: List[int] = []
self.pre: Optional[TreeNode] = None
self.maxCount: int = 0
self.count: int = 0
def traversal(self, cur: Optional[TreeNode]) -> None:
if not cur:
return
self.traversal(cur.left) # 左
if self.pre == None: # 中
self.count = 1
elif self.pre.val == cur.val:
self.count += 1
else:
self.count = 1
self.pre = cur
if self.count == self.maxCount:
self.result.append(cur.val)
elif self.count > self.maxCount:
self.maxCount = self.count
self.result.clear() # 清空result结果集
self.result.append(cur.val)
self.traversal(cur.right) # 右
return
def findMode(self, root: Optional[TreeNode]) -> List[int]:
self.traversal(root)
return self.result
1.23二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return None
if root == p or root == q:
return root
left: TreeNode = self.lowestCommonAncestor(root.left, p, q) # 左
right: TreeNode = self.lowestCommonAncestor(root.right, p, q) # 右
if left != None and right != None: # 中
return root
elif left != None and right == None:
return left
elif left == None and right != None:
return right
else:
return None
1.24二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:当我们从上往下遍历的时候,如果,遇到一个节点的值在p
和q
之间的话,那么就一定能说明这是这棵二叉搜索树的最近公共祖先吗?这一点确实能保证当前的节点就是二叉搜索树的最近公共祖先,因为如果当前的节点的值满足在p
和q
之间,就表明p
和q
一定分别在当前节点的左右子树中,所以这个节点就一定是最近公共祖先。
解法一:递归法
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return None
if root.val > p.val and root.val > q.val: # 左
left: TreeNode = self.lowestCommonAncestor(root.left, p, q)
if left != None:
return left
if root.val < p.val and root.val < q.val: # 右
right: TreeNode = self.lowestCommonAncestor(root.right, p, q)
if right != None:
return right
return root
解法二:迭代法
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return None
while root:
if root.val > p.val and root.val > q.val:
root = root.left
elif root.val < p.val and root.val < q.val:
root = root.right
else:
return root
1.25二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
解法一:迭代法+双指针
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 防止传入空指针
return TreeNode(val)
cur: Optional[TreeNode] = root
pre: Optional[TreeNode] = None
while cur:
pre = cur
if cur.val > val:
cur = cur.left
elif cur.val < val:
cur = cur.right
if pre.val > val:
pre.left = TreeNode(val)
elif pre.val < val:
pre.right = TreeNode(val)
return root
解法二:递归法
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root == None:
return TreeNode(val)
if root.val > val: # 左
left: TreeNode = self.insertIntoBST(root.left, val)
root.left = left
elif root.val < val: # 右
right: TreeNode = self.insertIntoBST(root.right, val)
root.right = right
return root
1.26删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
思路:我们需要考虑查找节点的几种情况,然后分情况讨论
- 情况一:没找到要删除的节点
- 情况二:待删除的节点是叶子节点
- 情况三:待删除的节点左不为空右为空
- 情况四:待删除的节点左为空右不为空
- 情况五:待删除的节点左不为空右不为空
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if not root:
return None
if root.val == key:
if root.left == None and root.right == None: # 情况二:待删除的节点是叶子节点
return None
elif root.left != None and root.right == None: # 情况三:待删除的节点左不为空右为空
return root.left
elif root.left == None and root.right != None: # 情况四:待删除的节点左为空右不为空
return root.right
else: # 情况五:待删除的节点左不为空右不为空
cur: Optional[TreeNode] = root.right
while cur.left:
cur = cur.left
cur.left = root.left
return root.right
elif root.val > key:
root.left = self.deleteNode(root.left, key)
elif root.val < key:
root.right = self.deleteNode(root.right, key)
return root # 情况一:没找到要删除的节点
1.27修剪二叉搜索树
给你二叉搜索树的根节点 root
,同时给定最小边界low
和最大边界 high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
class Solution:
def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
if not root:
return None
if root.val < low: # 向右修剪
return root.right # 这样写是不对的,因为当前root的right中不全是符合区间条件的node
elif root.val > high: # 向左修剪
return root.left
else: # 接收返回值
root.left = self.trimBST(root.left, low, high)
root.right = self.trimBST(root.right, low, high)
return root
正确的Solution
class Solution:
def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
if not root:
return None
if root.val < low: # 向右修剪
right: TreeNode = self.trimBST(root.right, low, high)
return right
elif root.val > high: # 向左修剪
left: TreeNode = self.trimBST(root.left, low, high)
return left
else: # 接收返回值
root.left = self.trimBST(root.left, low, high)
root.right = self.trimBST(root.right, low, high)
return root
1.28将有序数组转换成二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
递归法:左闭右闭
class Solution:
def traversal(self, nums: List[int], left: int, right: int) -> Optional[TreeNode]:
"""
当前定义的区间是左闭右闭的
"""
if left > right:
return None
mid: int = (left + right) // 2 # 这里一定是整除
root: Optional[TreeNode] = 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]:
return self.traversal(nums, 0, len(nums)-1)
递归法:左闭右开
class Solution:
def traversal(self, nums: List[int], left: int, right: int) -> Optional[TreeNode]:
"""
当前定义的区间是左闭右开的
"""
if left >= right:
return None
mid: int = (left + right) // 2 # 这里一定是整除
root: Optional[TreeNode] = TreeNode(nums[mid])
root.left = self.traversal(nums, left, mid)
root.right = self.traversal(nums, mid+1, right)
return root
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
return self.traversal(nums, 0, len(nums))
1.29把二叉搜索树转换成累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node
的新值等于原树中大于或等于 node.val
的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
给定一个二叉搜索树 root
(BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。
提醒一下, 二叉搜索树 满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
这两道题目又是一样的题目。
思路:使用中序遍历的逆序,为什么呢?因为对二叉搜索树使用中序遍历,我们就能获得一个有序的数组,为了从高到低处理节点,我们使用中序遍历的逆序,这样直接处理的就是节点值最大的节点了。
解法一:递归法
class Solution:
def __init__(self):
self.pre: Optional[TreeNode] = None
def traversal(self, cur: Optional[TreeNode]) -> None:
"""
中序遍历的逆序
"""
if not cur:
return None
self.traversal(cur.right) # 右
if self.pre:
cur.val += self.pre.val # 中
self.pre = cur
self.traversal(cur.left) # 左
def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None
self.traversal(root)
return root
解法二:迭代法
class Solution:
def __init__(self):
self.pre: Optional[TreeNode] = None
def traversal(self, cur: Optional[TreeNode]) -> None:
if not cur:
return None
stack: List[TreeNode] = []
while cur or stack:
if cur:
stack.append(cur)
cur = cur.right # 右
else:
cur = stack.pop() # 中
if self.pre:
cur.val += self.pre.val
self.pre = cur
cur = cur.left # 左
def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
self.traversal(root)
return root