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

目录

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

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

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

感想


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

文档讲解:代码随想录

视频讲解:二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先_哔哩哔哩_bilibili

状态:上一期做了普通二叉树的,这道题怎么用上二叉搜索树的特性呢?先把普通二叉树的解法默写了一遍,在二叉搜索树也适用。要判断大小?判断了又怎么了呢?

看了题解,完全跳出了上一道题目。利用搜索二叉树有序的特点,从上向下遍历,当节点置于[p q]区间内时,它就是公共祖先,而且还是最近公共祖先(举例表明的)。节点5是p和q的最近公共祖先。

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

递归遍历顺序,本题没有中节点的处理逻辑,不涉及到 前中后序了,遍历顺序无所谓了。

当当前节点的值大于p q时,往左子树去遍历;当节点小于p q时,往右子树去遍历(目标区间在右子树)。

不需要遍历整棵树,找到结果直接返回!

本题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

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

        if root.val > p.val and root.val > q.val:  # 不知道p和q谁大,所以两个都要判断
            left = self.lowestCommonAncestor(root.left,p,q)
            if left:
                return left
        elif root.val < p.val and root.val < q.val:
            right = self.lowestCommonAncestor(root.right,p,q)
            if right:
                return right
        else:
            return root   # root在p q之间

二叉搜索树的迭代法可能都比较简单,因为其有序性,可以给我们提供方向性

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while root:
            if root.val > p.val and root.val > q.val: 
                root = root.left    # 迭代法中是节点的移动
            elif root.val < p.val and root.val < q.val:
                root = root.right
            else:
                return root
        return None

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

文档讲解:代码随想录

视频讲解:原来这么简单? | LeetCode:701.二叉搜索树中的插入操作_哔哩哔哩_bilibili

状态:提示说简单,但没感觉到很简单啊~~

用迭代法的话,不知道怎么让root回到最初 以返回根节点;

用递归法的话,提交出错了,因为都考虑成是叶子节点然后再在后面添加一个节点了,还有比如说根节点但其左子节点(左子树)为空,需要在此处添加的情况。

修改之后又出错了,当原本是一个空树时,没能成功往其中添加节点,但我明明写了if not root:   return TreeNode(val),这使得我发现我没有正确处理递归函数是否有返回值的问题。

三类情况,感觉这题不太好归类。我写的是没有返回值,但在终止条件中写了if not root:   return TreeNode(val) 又没有发挥作用,所以还是有返回值的,否则终止条件中是单纯的return就可以了。我应该是没有在递归函数中接住返回值,导致这个语句没有发挥作用?不知道怎么改,都改成没有返回值的也不对。

我比照测试用例,在脑子中一步步执行程序,发现执行的也不顺当,不太合理。

递归法尝试:

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        
        self.traversal(root,val)
        return root


    def traversal(self,root,val):
        if not root:
            return TreeNode(val)
        
        if root.val > val and not root.left:
            root.left = TreeNode(val)
        elif root.val < val and not root.right:
            root.right = TreeNode(val)


        if root.val > val:
            self.traversal(root.left,val)

        if root.val < val:
            self.traversal(root.right,val)

递归法正确:

通过递归函数返回值完成新加入节点的父子关系赋值操作!

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:       # 找到遍历的节点为null的时候,就是要插入节点的位置了
            return TreeNode(val)  # 把插入的节点返回
         
        if root.val > val:  # 利用二叉搜索树的有序性
            root.left = self.insertIntoBST(root.left,val)  
        if root.val < val:
            root.right = self.insertIntoBST(root.right,val)
        # 下一层将加入节点返回,本层用root->left或者root->right将其接住
        
        return root

相比于我写的错误方法,正确方法 进行了许多的合并,首先有返回值不用另写一个函数了;not root就是判断为空,不用not root.left或者not root.right了;root.val 和val的比较也重复了,用root.left  root.right接住递归的返回值,返回值也就是not root时返回的TreeNode(val)。

我的思考:

所以由于二叉搜索树的特殊性,并不用完整遍历二叉树,但递归函数也有返回值。比较特殊,和那三类情况不太一样。

迭代法尝试:

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:
            return TreeNode(val)
        
        while root:
            if root.val > val:
                root = root.left
            elif root.val < val:
                root = root.right
        root = TreeNode(val) # 此时root来到了NULL的位置,也就是应该放置新节点的位置,我进行了赋值
        # 但不知道怎么返回整棵树的根节点了,而且忽略了节点要插入,这并没有将新节点和二叉树连接起来

迭代法正确:

也需要用双指针!需要记录一下当前遍历的节点的父节点,才能进行插入节点的操作。取指针cur parent 进行移动,避免改变root,方便最终的返回。

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:   # 二叉树为空
            return TreeNode(val)
        
        cur = root
        parent = root
        while cur:
            parent = cur  # 记录 更新 当前节点的父节点
            if cur.val > val:
                cur = cur.left
            elif cur.val < val:
                cur = cur.right
        
        # cur此时指向NULL
        if parent.val > val:
            parent.left = TreeNode(val)  # 将新节点连接到父节点的左子树
        elif parent.val < val:
            parent.right = TreeNode(val) # 将新节点连接到父节点的右子树
        
        return root
        

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

文档讲解:代码随想录

视频讲解:调整二叉树的结构最难!| LeetCode:450.删除二叉搜索树中的节点_哔哩哔哩_bilibili

状态:上一题只是单纯的插入节点,一定可以在叶子节点的位置找到要添加节点的位置,这一题还涉及到更改树的结构。

不知道删除的操作怎么进行,我在处理节点的左右皆不为空的情况时,没有妥善处理节点的左子树。删除节点的操作确实是放在了终止条件里这是对的!五种情况我也都想到了!

通过递归返回值删除节点

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root:   # 没找到要删的节点 或二叉树为空
            return None

        if root.val == key:
            if not root.left and not root.right: # 删的节点是叶子节点
                return None                      # 不是root = None 而是return None
            elif not root.left and root.right:  # 删的节点左为空 右不空
                return root.right
            elif not root.right and root.left:  # 删的节点右为空 左不空
                return root.left               # 不是root = root.left 而是return root.left 返回给root父节点的,作为其左子树
            else:                               # 左右皆不为空
                cur = root.right    # cur指向当前节点root的右子树的根节点  也就是root的右节点
                while cur.left:
                    cur = cur.left   # 找到 右子树最左侧的节点,因为它是比左子树值大一点点的
                cur.left = root.left  # 将root的左子树放在 右子树最左侧节点的下面 即可保持搜索二叉树性质的不变
                return root.right        # 右子树继位 删除root

        # 二叉搜索树的有序性 可以规划搜索方向
        if root.val > key:
            root.left = self.deleteNode(root.left,key) # 相当于把新的节点返回给上一层,上一层用root.left 接住返回的 节点
        if root.val < key:
            root.right = self.deleteNode(root.right,key)
        
        return root                              
        

进阶:

普通二叉树删除节点怎么做?

感想

学习用时:下午2.5h+晚上3h

你可能感兴趣的:(算法,leetcode,职场和发展,python)