Leetcode刷题笔记23:二叉树9(二叉搜索树的删除、修建与构建)

108 阅读4分钟

导语

leetcode刷题笔记记录,本篇博客记录二叉搜索树部分的题目,主要题目包括:

  • 450 删除二叉搜索树中的节点
  • 669 修剪二叉搜索树
  • 108 将有序数组转换为二叉搜索树
  • 538 把二叉搜索树转换为累加树

Leetcode 450 删除二叉搜索树中的节点

题目描述

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 1:

输入: root = [5,3,6,2,4,null,7], key = 3
输出: [5,4,6,2,null,null,7]
解释: 给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。

解法

删除一个节点需要分为以下五种情况:

  1. 待删除的节点不存在:直接返回空;
  2. 待删除的节点为叶子节点:直接返回空;
  3. 待删除的节点左孩子为空:直接返回右孩子节点;
  4. 待删除的节点右孩子为空:直接返回左孩子节点;
  5. 待删除的节点左孩子、右孩子都不为空:将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

代码设计如下:

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        # 第一种情况,找不到这个值
        if root is None:  
            return None
        if root.val == key:
            # 第二种情况,叶子节点
            if root.left is None and root.right is None:
                return None
            # 第三中情况,左不为空,右为空
            elif root.right is None:
                return root.left
            # 第四种情况,左为空,右不为空
            elif root.left is None:
                return root.right
            # 第五种情况
            else:
                cur = root.right
                while cur.left:
                    cur = cur.left
                cur.left = root.left
                return root.right
            
        if key < root.val:
            root.left = self.deleteNode(root.left, key)
        else:
            root.right = self.deleteNode(root.right, key)

        return root

Leetcode 669 修剪二叉搜索树

题目描述

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

示例 1:

输入: root = [1,0,2], low = 1, high = 2
输出: [1,null,2]

解法

这道题目比较难,因为我们在处理某个节点值不在区间时一种最简单的方式是之间将这个节点及其左右子树全部删除。然而,这样做是错误的。 例如下面的这个例子:

image.png 假如我们在遍历0节点,而区间范围给的是[1,3],那么如果仅因为0小于1就直接删除0-2-1则是完全错误的,为此,我们在处理时,需要考虑:

  1. 若当前节点值<low,那么需要进一步处理它的右子树;
  2. 若当前节点值>high,那么需要进一步处理它的左子树。

具体代码实现如下:

class Solution:
    def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
        if root is None: return None
        if root.val < low:
            right = self.trimBST(root.right, low, high)
            return right
        if root.val > high:
            left = self.trimBST(root.left, low, high)
            return left
        root.left = self.trimBST(root.left, low, high)
        root.right = self.trimBST(root.right, low, high)

        return root

Leetcode 108 将有序数组转换为二叉搜索树

题目描述

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

输入: nums = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
解释: [0,-10,5,null,-3,null,9] 也将被视为正确答案:

解法

这道题目与juejin.cn/post/726569… 里的最大二叉树很像,都是直接划分区间传入构建左右孩子,所以遍历顺序为“中左右”。

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        if  not nums:
            return None
        mid = len(nums) // 2 
        root = TreeNode(nums[mid])
        root.left = self.sortedArrayToBST(nums[:mid])
        root.right = self.sortedArrayToBST(nums[mid+1:])

        return root

Leetcode 538 把二叉搜索树转换为累加树

题目描述

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

示例 1:

输入: [4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出: [30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

解法

暴力法如下:先中序遍历得到有序数组,然后从后向前拿到累加和,最后再遍历一遍将节点值覆盖。

class Solution:
    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        nums = self.inorderTraversal(root)
        cum_sum = 0
        num2cum = dict()
        for i in range(len(nums)-1, -1, -1):
            cum_sum += nums[i]
            num2cum[nums[i]] = cum_sum
        self.inorderTraversal_update(root, num2cum)
        return root
        
    def inorderTraversal_update(self, root, num2cum):
        if root is None:
            return
        root.val = num2cum[root.val]
        self.inorderTraversal_update(root.left, num2cum)
        self.inorderTraversal_update(root.right, num2cum)


    def inorderTraversal(self, root):
        if root is None:
            return []
        return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)

使用递归法求解时,我们需要从右侧节点开始处理,所以整个递归的流程应该是“右-中-左”,具体代码如下:

class Solution:
    def __init__(self):
        self.pre_val = 0

    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root is None:
            return None
            
        self.convertBST(root.right)  # 右
        root.val += self.pre_val     # 中
        self.pre_val = root.val
        self.convertBST(root.left)   # 左

        return root