代码随想录训练营 Day17打卡 二叉树 part05
一、 力扣654. 最大二叉树
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。
示例 :
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:
递归调用如下所示:
- [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
- 空数组,无子节点。
- [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
- 空数组,无子节点。
- 只有一个元素,所以子节点是一个值为 1 的节点。
- [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
- 只有一个元素,所以子节点是一个值为 0 的节点。
- 空数组,无子节点。
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。最大二叉树的构建过程如下:
版本一 基础版
实现思路:这个方法遍历数组找到最大值和它的索引,然后递归地使用最大值左侧和右侧的子数组来构建当前节点的左子树和右子树。
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
if len(nums) == 1:
return TreeNode(nums[0]) # 如果数组长度为1,直接返回该元素构成的节点
node = TreeNode(0) # 初始化节点
# 找到数组中最大的值和对应的下标
maxValue = 0
maxValueIndex = 0
for i in range(len(nums)):
if nums[i] > maxValue:
maxValue = nums[i]
maxValueIndex = i
node.val = maxValue # 设置节点值为最大值
# 最大值左边的数组构造左子树
if maxValueIndex > 0:
node.left = self.constructMaximumBinaryTree(nums[:maxValueIndex])
# 最大值右边的数组构造右子树
if maxValueIndex < len(nums) - 1:
node.right = self.constructMaximumBinaryTree(nums[maxValueIndex + 1:])
return node
版本二 使用下标
实现思路:此版本优化了空间使用,避免了数组切片,而是通过索引来操作。递归函数处理从left到right的子数组,寻找最大值并递归构建子树。
class Solution:
def traversal(self, nums: List[int], left: int, right: int) -> TreeNode:
if left >= right:
return None # 如果当前子数组为空,返回None
maxValueIndex = left
for i in range(left + 1, right):
if nums[i] > nums[maxValueIndex]:
maxValueIndex = i
root = TreeNode(nums[maxValueIndex]) # 创建根节点为最大值
# 递归创建左右子树
root.left = self.traversal(nums, left, maxValueIndex)
root.right = self.traversal(nums, maxValueIndex + 1, right)
return root
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
return self.traversal(nums, 0, len(nums))
版本三 使用切片
实现思路:此版本直接使用Python的切片功能和内置函数来简化代码。它在每次递归中通过max函数和index方法找到最大值及其索引,然后分别对左右子数组进行递归,构建左右子树。
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
if not nums:
return None # 如果数组为空,返回None
max_val = max(nums) # 找到数组中的最大值
max_index = nums.index(max_val) # 找到最大值的索引
node = TreeNode(max_val) # 创建节点
# 使用切片递归构建左右子树
node.left = self.constructMaximumBinaryTree(nums[:max_index])
node.right = self.constructMaximumBinaryTree(nums[max_index + 1:])
return node
力扣题目链接
题目文章讲解
题目视频讲解
二、 力扣617. 合并二叉树
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
示例 :
输入:root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7]
输出:[3,4,5,5,4,null,7]
本题使用哪种遍历都是可以的!我们下面以前序遍历为例。
动画如下:
版本一 递归 - 前序 - 修改root1
实现思路:
- 递归地合并两棵树。
- 在每个节点上,将 root2 的节点值加到 root1 的相应节点上。
- 递归合并每个节点的左子树和右子树。
- 返回修改后的 root1 作为结果。
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
# 如果其中一个树为空,直接返回另一个树作为结果
if not root1:
return root2
if not root2:
return root1
# 将root2的值加到root1上,这样就可以避免创建新节点,节约空间
root1.val += root2.val
# 递归合并左子树
root1.left = self.mergeTrees(root1.left, root2.left)
# 递归合并右子树
root1.right = self.mergeTrees(root1.right, root2.right)
# 返回修改后的root1作为合并后的树的根节点
return root1
版本二 递归 - 前序 - 新建root
实现思路:
- 与版本一类似,但不修改原始树的节点,而是为每个合并后的节点创建新的节点。
- 适用于情况中要求不更改原有树的结构。
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
# 特殊情况处理
if not root1:
return root2
if not root2:
return root1
# 创建新节点作为合并后的新树的根节点
root = TreeNode(root1.val + root2.val)
# 递归创建左右子树
root.left = self.mergeTrees(root1.left, root2.left)
root.right = self.mergeTrees(root1.right, root2.right)
return root
版本三 迭代
实现思路:
- 使用队列进行层次遍历。
- 逐对节点进行合并。
- 如果两个节点都有子节点,将它们加入队列继续合并。
- 如果某个节点的子节点为空,直接取另一个节点的子节点。
from collections import deque
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
# 如果root1为空,返回root2作为合并结果
if not root1:
return root2
# 如果root2为空,返回root1作为合并结果
if not root2:
return root1
# 初始化队列,将两棵树的根节点加入队列
queue = deque()
queue.append(root1)
queue.append(root2)
# 当队列不为空,继续执行循环
while queue:
# 弹出两个节点进行合并
node1 = queue.popleft()
node2 = queue.popleft()
# 将node2的值加到node1上,实现节点的合并
node1.val += node2.val
# 如果两个节点的左子树都存在,将它们的左子节点分别加入队列
if node1.left and node2.left:
queue.append(node1.left)
queue.append(node2.left)
# 如果node1的左子树不存在,直接将node2的左子树挂到node1上
elif not node1.left:
node1.left = node2.left
# 同理,处理右子树
if node1.right and node2.right:
queue.append(node1.right)
queue.append(node2.right)
# 如果node1的右子树不存在,直接将node2的右子树挂到node1上
elif not node1.right:
node1.right = node2.right
# 返回合并后的树的根节点
return root1
力扣题目链接
题目文章讲解
题目视频讲解
三、 力扣700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
示例 :
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
这就决定了,二叉搜索树,递归遍历和迭代遍历和普通二叉树都不一样。本题,其实就是在二叉搜索树中搜索一个节点。
对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。中间节点如果大于3就向左走,如果小于3就向右走,如图:
版本一 递归
实现思路:
- 使用递归方法,从根节点开始。
- 每次根据当前节点值与目标值的比较,决定是进入左子树还是右子树进行搜索。
- 当找到目标节点或到达叶子节点但未找到目标时停止搜索。
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
# 如果节点为空或者节点的值等于搜索值,则返回该节点
if not root or root.val == val:
return root
# 如果当前节点的值大于搜索值,搜索左子树
if root.val > val:
return self.searchBST(root.left, val)
# 如果当前节点的值小于搜索值,搜索右子树
if root.val < val:
return self.searchBST(root.right, val)
版本二 迭代
实现思路:
- 不使用递归,而是使用迭代的方式。
- 通过循环和条件判断,逐步向下移动到合适的子节点。
- 当找到值匹配的节点或者无法再向下移动时(即root变为None),循环结束。
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
# 使用循环,从根节点开始遍历树
while root:
# 如果当前节点的值大于目标值,移动到左子节点
if val < root.val:
root = root.left
# 如果当前节点的值小于目标值,移动到右子节点
elif val > root.val:
root = root.right
# 如果相等,找到了目标节点,返回该节点
else:
return root
# 如果遍历完整棵树都没有找到,返回None
return None
版本三 栈-遍历
实现思路:
- 使用栈来进行深度优先搜索。
- 不同于方法二的是,这里不区分左右子节点的压栈顺序,而是同时考虑两个方向。
- 每次从栈中取出一个节点,检查它的值是否匹配,如果不匹配,则将其子节点压栈继续搜索。
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
stack = [root] # 初始化栈,并将根节点压入栈
while stack:
node = stack.pop() # 弹出栈顶元素
if node.val == val: # 如果节点的值等于搜索值,返回该节点
return node
# 如果当前节点有右子节点,将右子节点压入栈
if node.right:
stack.append(node.right)
# 如果当前节点有左子节点,将左子节点压入栈
if node.left:
stack.append(node.left)
return None # 如果栈空了还没找到,返回None
力扣题目链接
题目文章讲解
题目视频讲解
四、 力扣98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 :
输入:root = [2,1,3]
输出:true
版本一 递归法 利用中序递增性质,转换成数组
实现思路:
- 使用中序遍历收集树中的所有节点值。
- 因为对于二叉搜索树,中序遍历结果应该是严格递增的。
- 通过比较相邻元素确保整个数组是递增的。
class Solution:
def __init__(self):
self.vec = [] # 初始化空数组用于存储中序遍历的结果
def traversal(self, root):
if root is None:
return
self.traversal(root.left) # 访问左子树
self.vec.append(root.val) # 将节点值加入数组
self.traversal(root.right) # 访问右子树
def isValidBST(self, root):
self.vec = [] # 清空数组以备新的验证
self.traversal(root) # 执行中序遍历
# 检查数组是否严格递增
for i in range(1, len(self.vec)):
if self.vec[i] <= self.vec[i - 1]:
return False
return True
版本二 递归法 设定极小值,进行比较
实现思路:
- 使用中序遍历的过程中,利用一个变量记录前一个节点的值。
- 确保每个节点的值都大于之前的值。
- 这种方法避免了数组存储,空间复杂度更优。
class Solution:
def __init__(self):
self.maxVal = float('-inf') # 初始化为负无穷大
def isValidBST(self, root):
if root is None:
return True
# 先递归检查左子树
left = self.isValidBST(root.left)
# 检查当前节点的值是否大于前一个节点的值
if self.maxVal < root.val:
self.maxVal = root.val # 更新为当前节点的值
else:
return False
# 再递归检查右子树
right = self.isValidBST(root.right)
return left and right
版本三 递归法 直接取该树的最小值
实现思路:
类似于版本二,但在这里使用self.pre直接引用前一个节点,而不是其值。
这样可以直接比较节点的值而无需额外的值传递。
class Solution:
def __init__(self):
self.pre = None # 用于记录前一个节点
def isValidBST(self, root):
if root is None:
return True
# 验证左子树
left = self.isValidBST(root.left)
# 验证当前节点
if self.pre is not None and self.pre.val >= root.val:
return False
self.pre = root # 更新前一个节点
# 验证右子树
right = self.isValidBST(root.right)
return left and right
版本四 迭代法
实现思路:
- 使用栈进行中序遍历。
- 在遍历过程中比较当前节点与前一个访问的节点的值。
- 保证每次访问的节点的值都必须大于前一个节点的值。
class Solution:
def isValidBST(self, root):
stack = []
cur = root
pre = None # 记录前一个节点
while cur is not None or stack:
while cur:
stack.append(cur)
cur = cur.left # 深入访问左子树
cur = stack.pop() # 访问节点
if pre is not None and cur.val <= pre.val:
return False
pre = cur # 更新前一个节点
cur = cur.right # 进入右子树
return True
力扣题目链接
题目文章讲解
题目视频讲解