【算法-LeetCode】450. 删除二叉搜索树中的节点(二叉树;二叉搜索树)

450. 删除二叉搜索树中的节点 - 力扣(LeetCode)

文章起笔:2021年11月15日19:02:03

问题描述及示例

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

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

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

示例 1:

【算法-LeetCode】450. 删除二叉搜索树中的节点(二叉树;二叉搜索树)_第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], 如下图所示。

【算法-LeetCode】450. 删除二叉搜索树中的节点(二叉树;二叉搜索树)_第2张图片
另一个正确答案是 [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, 105].
  • -105 <= Node.val <= 105
  • 节点值唯一
  • root 是合法的二叉搜索树
  • -105 <= key <= 105


来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-bst
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的题解

解题的关键就是利用二叉搜索树中的节点值的分布规律:当前节点的节点值一定大于其左子树的任一节点的节点值;并且当前节点的节点值一定小于等于其右子树的任一节点的节点值。(也就是 LeftData < CurrentData < RightData 永远成立)。

详细的步骤比较难以用文字叙述,可以多画几张图来辅助理解。详细的过程解释可以参看:

『《数据结构——用C语言描述(第二版)》P271~P278 耿国华、张德同、周明全等编著——高等教育出版社』

这本教材中的相关内容,我也只是将里面的C语言代码按照自己的理解翻译成了JavaScript代码。

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} key
 * @return {TreeNode}
 */
var deleteNode = function(root, key) {
  // 为了应对key对应的节点恰好是原二叉树的根节点,我特意给root节点创建了一个父节点god
  // god.left就相当于是指向原二叉树的根节点root
  let god = new TreeNode(Infinity, root, null);
  // target是要删除的目标节点,需要我们通过搜索找到,其初始值为root
  let target = root;
  // targetParent 是目标节点的父节点,其初始值为god
  let targetParent = god;
  // preTarget是target在原二叉树的中序遍历序列中的直接前驱节点,它也需要在通过搜索找到
  // 也就是说,当我们获取到原二叉树的中序遍历序列后,preTarget就在target之前
  let preTarget = null;
  // preTargetParent是preTarget的父节点
  let preTargetParent = null;
  // child是一个辅助变量,用于简化后续的一点判断逻辑,其值为 'left' 或 'right'
  let child = '';
  // 下面while循环的逻辑就是用于寻找要删除的目标节点target
  while(target) {
    if(target.val === key) {
      break;
    }
    targetParent = target;
    target = target.val > key ? target.left : target.right;
  }
  // 如果二叉树中没有找到目标节点,则直接返回根节点
  if(!target) {
    return god.left;
  }
  // 如果找到的目标节点没有左子树
  if(!target.left) {
    // 判断目标节点是其父节点的左子节点还是右子节点
    child = targetParent.left === target ? 'left' : 'right';
    // 将目标节点的右子树链接到目标节点的父节点的响应位置,
    // 这里就相当于是让目标节点的右子树顶替了原目标节点的位置
    targetParent[child] = target.right;
    // 释放目标节点,相当于是删除节点的操作
    target = null;
    // 最后返回删除目标节点后的二叉树的根节点,结束程序
    return god.left;
  }
  // 如果目标节点有左子树,则开始寻找preTarget和preTargetParent
  // 寻找preTarget的过程就相当于是寻找target节点的左子树中的最靠右的那个节点
  // 该节点在中序遍历序列中的位置就恰好是在target前面
  preTargetParent = target;
  preTarget = target.left;
  while(preTarget.right) {
    preTargetParent = preTarget;
    preTarget = preTarget.right;
  }
  // 判断该把preTarget的左子树链接到preTargetParent的左边还是右边
  // 如果preTarget恰好是target的左子节点,则链接到左边,否则链接到右边
  child = preTarget === target.left ? 'left' : 'right';
  preTargetParent[child] = preTarget.left;
  // 让preTarget节点的节点值顶替target节点的节点值
  target.val = preTarget.val;
  // 释放preTarget节点
  preTarget = null;
  // 最后返回删除目标节点后的二叉树的根节点,结束程序
  return god.left;
};


提交记录
执行结果:通过
执行用时:96 ms, 在所有 JavaScript 提交中击败了89.63%的用户
内存消耗:46.8 MB, 在所有 JavaScript 提交中击败了6.88%的用户
通过测试用例:91 / 91
时间:2021/11/15 19:08

一开始我没有考虑到当要删除的节点就是原二叉树的根节点时的情况,所以当时没有通过全部用例:

执行结果:
解答错误
通过测试用例:89 / 91
输入:[0], 0
输出:[0]
预期结果:[]
时间:2021/11/15 19:07

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

【更新结束】

更新:2021年11月15日19:10:01

参考:删除二叉搜索树中的节点 - 删除二叉搜索树中的节点 - 力扣(LeetCode)

【更新结束】

有关参考

《数据结构——用C语言描述(第二版)》P271~P278 耿国华、张德同、周明全等编著——高等教育出版社

你可能感兴趣的:(LeetCode,leetcode,算法,javascript,二叉搜索树,bst)