【前端面试常见算法题系列】450. 删除二叉搜索树中的节点(中等)

107 阅读3分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

一、题目描述

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

一般来说,删除节点可分为两个步骤:

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

示例 1: image.png

输入: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]。

示例 2:

输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点

示例 3:

输入: root = [], key = 0
输出: []

提示:

  • 节点数的范围 [0, 104]
  • -105 <= Node.val <= 105
  • 节点值唯一
  • root 是合法的二叉搜索树
  • -105 <= key <= 105

进阶: 要求算法时间复杂度为 O(h)h 为树的高度。

二、思路分析

抓住“二叉搜索树” 左小右大 的特点,我们判断遍历到的节点的值与 key 的大小关系,根据这个关系选择遍历路径。然后在这条路径上找到目标节点后,就可以准备删除该节点了。

需要注意,要删除的节点有三种情况:

  • ①该节点恰好是末端节点,不存在左节点和有节点,返回 nullptr 即可。
  • ②该节点只有一个子节点,若没有左节点则将右节点作为取代节点,若没有右节点则将左节点作为取代节点。
  • ③该节点有两个子节点,我们要保证在删除了该节点后,这棵树依然是一颗二叉搜索树,因此需要找到新的节点来替代被删除的节点。要达到这个要求,有两种策略:
    • 遍历该节点的左子树的右子树,遍历到的末端节点就为取代节点。

    二叉搜索树的根节点的左节点肯定小于右节点,因此不管要删除的节点的左子树遍历到什么程度,节点值都肯定小于要删除节点的右节点值;但如果遍历到末尾,这个末端节点值肯定是大于要删除节点的左节点。

    • 遍历该节点的右子树的左子树,遍历到的末端节点就为取代节点。

    右子树的遍历也是同样的道理。

清楚了这三种情况后,我们可以来删除节点了,思路如下:

  • 找到要删除的节点 root ,寻找 root 的右子树的最小节点 cur(选择第二种策略)。
  • root 的左子树放在 cur 左子树的位置(砍掉 root 的左子树,就剩下右子树没有砍了)。
  • 由于我们是递归查看节点的,那么右子树的处理就看最后要 return 什么了。这里先给结论 return root -> right ,为什么是这样呢?不应该是 return cur 吗?其实不然,因为如果 cur 接上 root 左子树之后,加上它本身的右子树(如果有的话)和它的根节点,就已经有三个连接了,如果将 cur 作为取代节点的话,就不满足二叉树的定义了,因此不能 return cur。那么这样一来,最好的选择就是 root -> right ,将其作为取代节点,因为它本身就满足 左大右小 的特点,并且在 root 被删除掉之后,本来与 root 的连接就断开了,此时它最多有两个连接而已,作为取代节点再合适不过了。

最难的部分已经攻克了,答案也不难写了。

三、AC 代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return nullptr;

        if (root -> val > key) root-> left = deleteNode(root -> left, key);
        if (root -> val < key) root -> right = deleteNode(root -> right, key);
        if (root -> val == key) {
            if (!root -> left && !root -> right) return nullptr;
            if (!root -> left) return root -> right;
            if (!root -> right) return root -> left;

            // 找到右子树最小的节点
            TreeNode* cur = root -> right;
            while (cur -> left) {
                cur = cur -> left;
            }
            cur -> left = root -> left;
            return root -> right;
        }
        return root;
    }
};