代码随想录算法训练营第二十一天 | 235. 二叉搜索树的最近公共祖先、701. 二叉搜索树中的插入操作、450. 删除二叉搜索树中的节点

70 阅读3分钟

235. 二叉搜索树的最近公共祖先

链接

文章链接

题目链接

第一想法

由于昨天写过查询两个节点的最近公共节点,所以首先想到的也是昨天的方法,尝试练习一下,代码如下:

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
	const foo=(root: TreeNode | null)=>{
        if(root==p||root==q||root==null) return root
        let left: TreeNode | null=foo(root.left)
        let right: TreeNode | null=foo(root.right)
        if(left!=null&&right!=null) return root
        if(left==null) return right
        if(right==null) return left
    }
    return foo(root)
};

但是这样写就和普通二叉树没有区别了,所以估计要用到搜索二叉树的性质来解决这道题,目前能想到的是如果两个节点的最大值都小于root的话 那么只可能存在root.left当中,如果两个节点的最小值都大于root的话,那么只能存在root.right当中,如果一大一小 那么当前节点就是公共节点

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
  if(p!.val>q!.val){
    [p,q]=[q,p];
  }
const foo=(root: TreeNode | null)=>{
      if(root==p||root==q||root==null) return root
      if(root.val<p!.val) return foo(root.right)
      if(root.val>q!.val) return foo(root.left)
      let left: TreeNode | null=foo(root.left)
      let right: TreeNode | null=foo(root.right)
      if(left!=null&&right!=null) return root
      if(left==null) return right
      if(right==null) return left
  }
  return foo(root)
};

优化版:

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
    if(p!.val>q!.val){ //保证p小q大
      [p,q]=[q,p];
    }
	const foo=(root: TreeNode | null)=>{
        if(root==p||root==q||root==null) return root
        if(root.val<p!.val) return foo(root.right)
        if(root.val>q!.val) return foo(root.left)
        return root
    }
    return foo(root)
};

看完文章后的想法

文章的想法和我优化版的想法是一致的,所以就不复制代码了,因为搜索二叉树有一定的性质,那么按照搜索二叉树的性质可以分辨向左递归还是向右递归,因为这道题不涉及对中间节点的操作。所以不用区分前中后序。

思考

这道题可以按照普通查询公共节点的方法写,也可以利用搜索二叉树的性质来写,其中我首先利用普通方法熟悉过程,之后再利用搜索二叉树的性质来解决这道题,总体来说不难。

701. 二叉搜索树中的插入操作

链接

题目链接

文章链接

第一想法

跟着二叉搜索树的性质,如果val>root.val,则递归右孩子,否则递归左孩子,核心思想是要把节点返回出来,所以用root.left或者right去接返回值,代码如下:

function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null {
    const foo:(root: TreeNode | null)=>TreeNode=(root: TreeNode | null)=>{
    if(root==null) return new TreeNode(val) //如果root是null说明这个节点就是需要生成的节点
      if(root.val>val) root.left=foo(root.left)//接返回值
      if(root.val<val) root.right=foo(root.right)
      return root
    }
    return foo(root)
};

看完文章后的想法

文章关于地规范给出了两种方法,一种是有返回值,和我上面的思路是一样的,也有没有返回值的,没有返回值的思路是记录上一个节点,之后如果当前节点为null,则通过比较val和上一节点的大小来添加左或右孩子,代码如下:

function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null {
  if(root==null) return new TreeNode(val)
  let pre:TreeNode|null
  const foo=(root: TreeNode | null)=>{
  if(root==null){
      if(pre!.val>val) pre!.left=new TreeNode(val)
      else pre!.right=new TreeNode(val)
      return 
  }
  pre=root
    if(root.val>val) foo(root.left)
    if(root.val<val) foo(root.right)
  }
   foo(root)
  return root
};

思考

这道题也不是很难,如果利用二叉搜索树的性质很容易就能想到用递归找到该填入节点的位置,好像的方法是有返回值,直接将返回值接住就可以了,不容易想到的是直接修改节点,没有返回值,它需要借助一个指针才能完成操作。

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

链接

文章链接

题目链接

第一想法

这道题刚开始一看是挺难的,因为要删除节点之后,还要变为搜索二叉树,所以刚开始毫无头绪,但是想到了搜索二叉树地性质,所以有了一个想法,如果能找到这个节点,则让该节点的右孩子代替节点,(右孩子的所有节点都大于左孩子的节点),之后让节点的右孩子的最左节点接住删除节点的左孩子就行了,代码如下:

function deleteNode(root: TreeNode | null, key: number): TreeNode | null {
  if(root==null) return root //防止root==null的情况
  let pre: TreeNode | null=null //用于存储删除节点的左孩子
  const foo:(root: TreeNode | null)=>TreeNode|null=(root: TreeNode | null)=>{
      if(root==null) return pre //当删除节点的右孩子的左孩子达到最低时 这个左孩子的left接住删除节点的左孩子
      if(root.val>key) root.left=foo(root.left) //左孩子接住
      if(root.val<key) root.right=foo(root.right)//右孩子接住
      if(root.val===key){ //删除节点
          if(!root.right) return root.left //如果删除节点没有右孩子则返回左孩子
          else{
              if(!root.left) return root.right //没左孩子返回右孩子
              pre=root.left //存储左孩子
              key=root.left.val //这时候要把key变为左孩子的val 目的是要寻找到删除节点右孩子的最底层的左节点
              root.right.left=foo(root.right.left)
              return root.right //完成上述后返回右节点
          } 
      }
      return root
  }
  return foo(root)
};

看完文章后的想法

文章的想法和我是类似的,唯一不同的是对于删除节点的左右孩子都存在时的操作是不一样的,但是核心理念是一样的,但是文章更加具体的给出了5种情况:

  1. 没有找到
  2. 找到了 但是删除节点的左右孩子都为空
  3. 找到了 删除节点的左孩子为空
  4. 找到了 删除节点的右孩子为空
  5. 找到了 删除节点的左右孩子都不为空 详细代码如下:
function deleteNode(root: TreeNode | null, key: number): TreeNode | null {
  const foo:(root: TreeNode | null)=>TreeNode|null=(root: TreeNode | null)=>{
      if(root==null) return root
      if(root.val>key) root.left=foo(root.left)
      if(root.val<key) root.right=foo(root.right)
      if(root.val===key){
          if(!root.right) return root.left
          else if(!root.left) return root.right
          else{
             let  pre:TreeNode|null=root.right
             while(pre.left){ //文章中用的是while 而我的想法是用的递归
                 pre=pre.left
             }
              pre.left=root.left
              return root.right
          } 
      }
      return root
  }
  return foo(root)
};

思考

这道题有些难度,需要搞清楚可能出现的五种情况后,依次对五种情况进行处理就可以了,其中前四种情况比较好处理,只有第五种情况比较难处理,处理的核心是想是删除节点的右节点占位删除节点,删除节点的左孩子用删除节点右孩子的最左侧节点的left接住。

今日总结

今日耗时2.5小时,三道题都很有意思。