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

文章目录

  • 235. 二叉搜索树的最近公共祖先
  • 701. 二叉搜索树中的插入操作
  • 450. 删除二叉搜索树中的节点
    • 重构删除
    • 迭代删除

返回值是非常有效的传递信息的方式!尤其在二叉树的修改中,返回值可以帮助以重构的方式完成修改,而无需保持双指针进行修改(树的结构决定了双指针还需要父节点的方向,太繁琐)。

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

题目链接 | 解题思路

和 236 的思路是一致的,但是 BST 的特殊性质能够提供更快的剪枝:
在 BST 中,自上往下第一次遇到 min(p, q) < node.val < max(p, q) 的时候,就能确定该节点是 p 和 q 的最近公共祖先。否则,根据 BST 的有序性只需要遍历当前节点的一个子树即可。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':    
        if root == None:
            return None
        if root == p or root == q:
            return root

        min_val, max_val = min(p.val, q.val), max(p.val, q.val)
        if min_val > root.val:
            return self.lowestCommonAncestor(root.right, p, q)
        if max_val < root.val:
            return self.lowestCommonAncestor(root.left, p, q)

        return root

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

题目链接 | 解题思路

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

简化版本如下,函数的返回值体现了对新节点的赋值和对父节点的更新。单层递归的逻辑是标准的“遍历一条边”,更新当前节点的左右子树能够“接住”递归函数的返回值,来完成节点的添加。

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

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

题目链接 | 解题思路

删除节点要比插入节点复杂得多,因为会改变树的结构。

删除节点有非常多的终止情况,主要取决于删除节点的位置,要一一理清。

  1. 没有找到需要删除的节点,遍历到 None 直接返回 None。
  2. 找到了需要删除的节点
    • 该节点为叶子节点,左右均为 None,此时直接删除该节点,返回 None
    • 该节点为左空右不空,删除该节点,由右节点直接补位,返回右节点
    • 该节点为左不空右空,删除该节点,由左节点直接补位,返回左节点
    • 该节点为左不空右不空,删除该节点,任选以下一种
      • 将该节点的左子树拼接到右子树的最小节点的左边,返回右子树的根节点
      • 将该节点的右子树拼接到左子树的最大节点的右边,返回左子树的根节点
        代码随想录算法训练营Day22 | 235. 二叉搜索树的最近公共祖先 | 701. 二叉搜索树中的插入操作 | 450. 删除二叉搜索树中的节点_第1张图片

注意删除的方式是通过返回节点来实现的,这里的删除实际上是在重建整棵树的过程中删掉了特定节点:

  • 每个终止条件都会完整地在当前的节点所代表的树中删掉待删除的节点,然后将删除节点后的新树的根节点返回;
  • 对于没有找到删除节点的情况,不断向下遍历,直到遇到需要删除的节点时通过更新父节点的左右子树来执行删除。

不要依靠记录父节点来执行删除,会变得不幸。

题目并不难,但是分类的思路要清晰并且要能实现还是很不容易的。

重构删除

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if root == None:
            return None
        
        if root.val == key:
            # case 1: leaf node to delete
            if root.left == None and root.right == None:
                return None             # ignore this node when reconstruct the tree
            # case 2: left is None, right is not None
            elif root.left == None and root.right != None:
                return root.right
            # case 3: left is not None, right is None
            elif root.left != None and root.right == None:
                return root.left
            # case 4: a common node with both children 
            else:
                curr_node = root.right
                while (curr_node.left != None):
                    curr_node = curr_node.left
                curr_node.left = root.left
                return root.right
        
        # now all the termination cases are done, recursion if no termination
        if root.val > key:
            root.left = self.deleteNode(root.left, key)
        if root.val < key:
            root.right = self.deleteNode(root.right, key)
        return root

如果是在普通二叉树中进行节点删除,可以通过交换值的方式:

  1. 第一次将待删除节点的值与待删除节点的右子树的最左侧的节点值交换
  2. 再次遍历搜索,第二次遇到待删除节点的值时,直接将其删除即可(返回此次待删除节点的左节点)

迭代删除

这里的主函数中记录了待删除节点的父节点,以此来进行节点的删除,可能更容易想到,但实现起来实在是麻烦。

class Solution:
    def deleteOneNode(self, target: Optional[TreeNode]) -> Optional[TreeNode]:
        # case 1: meet the None 
        if target == None:
            return None
        # case 2: right is None, directly use left subtree to replace
        if target.right == None:
            return target.left
        # case 3: right is not None, find the smallest node in right subtree
        curr = target.right
        while (curr.left != None):
            curr = curr.left
        curr.left = target.left
        return target.right


    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if root == None:
            return None
        
        pre, curr = None, root          # pre records the parent of curr
        while (curr != None):
            if curr.val == key:
                break
            pre = curr
            if curr.val > key:
                curr = curr.left
            else:
                curr = curr.right
        
        # curr = root, the whole tree has only the root, or root is the node to delete
        if pre == None:            
            return self.deleteOneNode(curr)
        
        # need to know which way (left or right) is the curr relative to pre
        # case: curr = pre.left
        if pre.left != None and pre.left.val == key:
            pre.left = self.deleteOneNode(curr)
        
        # case: curr = pre.right
        if pre.right != None and pre.right.val == key:
            pre.right = self.deleteOneNode(curr)
        
        return root

你可能感兴趣的:(代码随想录算法训练营一刷,算法)