代码随想录 - Day29 - 最近公共祖先,插入操作,删除操作

代码随想录 - Day29 - 最近公共祖先,插入操作,删除操作

236. 二叉树的最近公共祖先

自底向上查找:后序遍历(左右中)+回溯
两种情况:

  1. p和q分别为某个节点的左右节点,那么这个节点就是它们的最近公共祖先
  2. p(q)为q§的子孙节点,那么作为祖先节点的p(q)就是它们的最近公共祖先
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root == q or root == p or not root:
            return root
        # 回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if left and right:          # 若p和q分别为左右节点,返回它们的根节点就可以了
            return root

        if not left and right:      # 只有右节点等于p或q
            return right
        elif left and not right:    # 只有左节点等于p或q
            return left
        else:                       # 左右节点都不是p或q
            return None

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

  • 把它当成普通二叉树,找二叉树的最近公共祖先。代码和上一题一样即可,本题就不再给出了。
  • 按照二叉搜索树的特点来找,右节点全部大于根节点,左节点全部小于根节点,那么从上往下搜索时:
    1. 如果pq均小于当前节点,那么继续遍历左子树,如果pq均大于当前节点,那么继续遍历右子树。
    2. 如果min(p.val, q.val) <= 当前节点 <= max(p.val, q.val),那么该节点为二叉搜索树的最近公共祖先
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root:
            return root
        if root.val > max(p.val, q.val):
            return self.lowestCommonAncestor(root.left, p, q)
        if root.val < min(p.val, q.val):
            return self.lowestCommonAncestor(root.right, p, q)
        if min(p.val, q.val) <= root.val <= max(p.val, q.val):
            return root

注意:如果前面写了if root == q or root == p or not root: return root,那么后面就只需要写<;如果前面写if not root: return root后面才要写<=,要注意对应。

# 精简一下,可以写成这样:
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root.val > max(p.val, q.val):
            return self.lowestCommonAncestor(root.left, p, q)
        elif root.val < min(p.val, q.val):
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root
# 或者这样:
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root.val > p.val and root.val > q.val:
            return self.lowestCommonAncestor(root.left, p, q)
        elif root.val < p.val and root.val < q.val:
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root

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

往简单的方向想。
其实不管插入拿个数值,都可以不改变树的结构,直接插入到空节点处。(不理解的话可以自己画图试一试)
所以写代码的时候就可以利用二叉搜索树的特点,根据其特点找到合适的空节点处进行插入操作。

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:
            node = TreeNode(val)
            return node
        if root.val > val:      # 待插入数值小于当前节点数值,向左继续搜索
            root.left = self.insertIntoBST(root.left, val)
        if root.val < val:      # 待插入数值大于当前节点数值,向右继续搜索
            root.right = self.insertIntoBST(root.right, val)
        return root       

不用返回值,找到插入的节点位置后直接让其父节点指向插入节点:

class Solution:
    def __init__(self):
        self.parent = None

    def traversal(self, cur, val):
        if cur is None:         # parent左孩子或者右孩子指向新插入的节点
            node = TreeNode(val)
            if val > self.parent.val:
                self.parent.right = node
            else:
                self.parent.left = node
            return

        self.parent = cur
        if cur.val > val:
            self.traversal(cur.left, val)
        if cur.val < val:
            self.traversal(cur.right, val)

    def insertIntoBST(self, root, val):
        self.parent = TreeNode(0)
        if root is None:
            return TreeNode(val)
        self.traversal(root, val)
        return root

迭代法:需要记录一下当前遍历的节点的父节点,这样才能做插入节点的操作

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:    # 如果根节点为空,创建新节点作为根节点并返回
            node = TreeNode(val)
            return node

        cur = root
        parent = root   # 记录上一个节点,用于连接新节点
        while cur:
            parent = cur
            if cur.val > val:
                cur = cur.left
            else:
                cur = cur.right

        node = TreeNode(val)
        if val < parent.val:
            parent.left = node  # 将新节点连接到父节点的左子树
        else:
            parent.right = node # 将新节点连接到父节点的右子树

        return root    

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

删除节点的五种情况:

  • 没找到删除的节点:
    • 遍历到空节点直接返回
  • 找到删除的节点:
    • 左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
    • 删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    • 删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    • 左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
# 返回要删除节点的下一个节点就算删除了
class Solution:
    def deleteNode(self, root, key):
        # 第一种情况:没找到删除的节点,遍历到空节点直接返回
        if root is None:
            return root
        if root.val == key:
            # 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
            if root.left is None and root.right is None:
                return None
            # 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
            elif root.left is None:
                return root.right
            # 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
            elif root.right is None:
                return root.left
            # 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置, 并返回删除节点右孩子为新的根节点。
            else:
                cur = root.right        # 找右子树最左面的节点
                while cur.left is not None:
                    cur = cur.left
                cur.left = root.left    # 把要删除的节点(root)左子树放在cur的左孩子的位置
                return root.right
        if root.val > key:
            root.left = self.deleteNode(root.left, key)
        if root.val < key:
            root.right = self.deleteNode(root.right, key)
        return root

普通二叉树的删除方式:遍历整棵树,用交换值的操作来删除目标节点。
代码中目标节点(要删除的节点)被操作了两次:

  • 第一次是和目标节点的右子树最左面节点交换。
  • 第二次直接被NULL覆盖了。

不好想,实操性不强

class Solution:
    def deleteNode(self, root, key):
        if root is None:            # 如果根节点为空,直接返回
            return root
        if root.val == key:         # 找到要删除的节点
            if root.right is None:  # 如果右子树为空,直接返回左子树作为新的根节点
                return root.left
            cur = root.right
            while cur.left:         # 找到右子树中的最左节点
                cur = cur.left
            root.val, cur.val = cur.val, root.val       # 将要删除的节点值与最左节点值交换
        root.left = self.deleteNode(root.left, key)     # 在左子树中递归删除目标节点
        root.right = self.deleteNode(root.right, key)   # 在右子树中递归删除目标节点
        return root

迭代法:

class Solution:
    def deleteOneNode(self, target: TreeNode) -> TreeNode:
        """
        将目标节点(删除节点)的左子树放到目标节点的右子树的最左面节点的左孩子位置上
        并返回目标节点右孩子为新的根节点
        是动画里模拟的过程
        """
        if target is None:
            return target
        if target.right is None:
            return target.left
        cur = target.right
        while cur.left:
            cur = cur.left
        cur.left = target.left
        return target.right

    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        if root is None:
            return root
        cur = root
        pre = None  # 记录cur的父节点,用来删除cur
        while cur:
            if cur.val == key:
                break
            pre = cur
            if cur.val > key:
                cur = cur.left
            else:
                cur = cur.right
        if pre is None:  # 如果搜索树只有头结点
            return self.deleteOneNode(cur)
        # pre 要知道是删左孩子还是右孩子
        if pre.left and pre.left.val == key:
            pre.left = self.deleteOneNode(cur)
        if pre.right and pre.right.val == key:
            pre.right = self.deleteOneNode(cur)
        return root

这道题不是很好理解,后续应该多看看多理解理解,掌握第一种递归方法即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值