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

写代码的第十七天
递归还是带点懵逼的
一定要记得有些题递归要向上有返回值的!切记!
我发现我写代码只要分的情况超过三个我就开始懵了,就不知道怎么写了。。。。

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

思路(递归)

之前的那道题是二叉树的最近公共祖先,这个是二叉搜索树的最近公共祖先,所以要利用上二叉搜索树的性质。
从跟结点开始遍历,如果这个值在p和q之间那就证明这个结点是二叉搜索树中p和q的最近公共祖先(他不会是次近,因为二叉搜索树的性质,跟结点左边的值一定小于跟结点,右边的值一定大于跟结点,所以不可能存在一个结点在p和q之间但又不是最大公共祖先,画个图试试就明白了),如果这个值小于p和q那就证明pq的最近公共祖先在这个结点的右侧,如果这个值大于p和q那么就证明pq的最近公共祖先在这个结点的左侧。
解决问题1:递归参数和返回值是什么?参数包括传入的结点,最开始肯定是跟结点,以及p和q,返回值要根据题意返回最近公共祖先。
解决问题2:终止条件是什么?当出现一个结点的值在p和q之间,终止并且返回这个结点的值。
解决问题3:单层递归逻辑是什么?如果遍历结点的值小于p和q那就继续向右递归,如果这个值大于p和q那么就继续向左递归。
错误第一版:测试用例没通过。看了一下这个测试用例,发现出问题的地方在于有一种情况没有考虑到,当p为q的公共祖先或者q为p的公共祖先这种情况没有考虑到。并且没有在递归调用的时候将结果返回,所以上层递归是不知道本次递归的结果的。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if (root.val > p.val and root.val < q.val) or (root.val < p.val and root.val > q.val):
            return root
        if root.val > p.val and root.val > q.val:
            left = self.lowestCommonAncestor(root.left,p,q)
        if root.val < p.val and root.val < q.val:
            right = self.lowestCommonAncestor(root.right,p,q)

正确代码:在这里加了一个判断条件,为了解决p为q的父结点或者q为p的父结点这种情况。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if (root.val > p.val and root.val < q.val) or (root.val < p.val and root.val > q.val):
            return root
        if root.val > p.val and root.val > q.val:
            left = self.lowestCommonAncestor(root.left,p,q)
            return left
        if root.val < p.val and root.val < q.val:
            right = self.lowestCommonAncestor(root.right,p,q)
            return right
        if root.val == p.val or root.val == q.val:
            return root

思路(迭代)

超级简单,从跟结点开始遍历,如果pq大于跟结点那么指针向右遍历,如果pq小于 跟结点那么指针向左遍历,否则返回当前子树的根结点。

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

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

思路(递归)

在二叉搜索树中插入任何一个结点都可以在叶子结点中找到他的位置。按照二叉搜索树的性质遍历找到符合位置的空结点然后插入结点。
解决问题1:递归参数和返回值是什么?参数包括传入的结点,以及要插入的值,返回值要根据题意返回新的根结点。
解决问题2:终止条件是什么?当遇到了空结点,就把新结点插入进去(这里可能会有疑问,为什么遇到空的就插入,万一不是呢,我们根据后面的单层递归逻辑一层一层的递归,到最后遇到的空结点一定是符合条件的)。
解决问题3:单层递归逻辑是什么?如果val是大于当前结点的值的那么递归右子树,如果小于那么递归左子树。
错误第一版:结点根本没插入进去,也就是没和父结点连接上。也就是在递归的时候没向上一层返回值。

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

正确代码

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

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

思路(递归)

自己是一点思路没有,一片混乱,感觉好多种情况根本写不完,看了卡哥的视频分五种情况解决,啊舒服,太舒服了。
情况1:删除的结点就是叶子结点(左右孩子都为空),那就直接删除,把其父结点的左孩子或右孩子指针之间变空即可。
情况2:删除的结点左孩子不空右孩子为空,那么将其父结点的left直接指向被删除结点的左孩子即可(就很像链表删除)。
情况3:删除的结点左孩子为空右孩子不空,那么将其父结点的right直接指向被删除结点的右孩子即可。
情况4:删除的结点左右孩子都不为空(这种最复杂,画个图其实大家就明明白了),那么将被删除结点的左子树(左边全部的结点按顺序)放到被删除结点的右子树的最左侧的叶子结点的left。(我们可以想一下,左右都不为空的结点,在二叉搜索树中左一定小于中一定小于右,所以中结点被删,左一定比右侧所有的结点都小,那右侧最小的结点一定在其最左侧最下面的叶子结点上,所以左一定要赋给这个结点的左子树,这样才保证更新完的树还是一个二叉搜索树)。
情况5:如果找不到被删除的结点,那么树结构不变。
解决问题1:参数和返回值是什么?参数是root以及要删除的结点的值,返回值是删除之后新的root。
解决问题2:终止条件是什么?按照上面说的五种情况写五个终止条件。
1️⃣如果root==None,直接return None。还是要解释一下这个地方,这是个递归的终止条件,相当于是最后一次递归,这个值是要返回给上一层的,不是说最后的输出结果就是None了。
2️⃣如果root.left!=None and root.right= =None,只需要上面说的指针改变的操作,所以直接返回给上一层相应的值。这里的返回值就是root.left。因为root是当前要删除的结点,他要返回给上一层递归的就是把他的左孩子赋值给其父结点的左孩子,所以返回值就是他自己的左孩子。
3️⃣同上,返回他自己的右孩子。
4️⃣如果root.left!=None and root.right!=None,这里要做的首先是找到该结点的右子树的最左侧的叶子结点(用一个while循环),然后将该结点的左子树放在这个结点的左孩子上,最后将这个结点的右孩子作为其父结点的右孩子。
解决问题3:单次递归逻辑是什么?如果val值大于root.val,那么向右侧递归;如果val值小于root.val,那么向左侧递归。
错误第一版:测试用例除了空的全没通过啊哈哈哈哈我疯啦。。。。都是缺少结点…啊啊啊啊啊啊啊啊只有在这个树中能找到值的情况下才能进行五种情况的判断啊啊啊啊!!!!

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if root == None:
            return None
        if root.left != None and root.right == None:
            return root.left
        if root.right != None and root.left == None:
            return root.right
        if root.left != None and root.right != None:
            cur = root
            while cur != None:
                cur = cur.left
            cur = root.left
            return root.right
        if val > root.val:
            root.right = self.deleteNode(root.right,val)
        if val < root.val:
            root.left = self.deleteNode(root.left,val)
        return root

错误第二版:第一个测试用例没通过。有一个结点变成null了。啊啊啊啊啊左右结点都是空的情况没写,我真服了啊啊啊啊。试了一下还是同样的错误,再想想奥。涉及到稍微复杂点的就是左右都不空的情况,检查一下这块吧。
错大发了:我们要找的是将要删除的结点的右子树中最左侧的叶子结点,所以cur指针应该初始化为将要删除的结点的右子树中!!!!我们一直要判断的是cur的left是不是空,一旦是空那么while循环停止,所以循环条件也错了!!!!

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if root == None:
            return None
        if root.val == key:
            if root.left != None and root.right == None:
                return root.left
            if root.right != None and root.left == None:
                return root.right
            if root.left != None and root.right != None:
                cur = root
                while cur != None:
                    cur = cur.left
                cur = root.left
                return root.right
        if key > root.val:
            root.right = self.deleteNode(root.right,key)
        if key < root.val:
            root.left = self.deleteNode(root.left,key)
        return root

正确代码

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if root == None:
            return None
        if root.val == key:
            if root.left != None and root.right == None:
                return root.left
            if root.right != None and root.left == None:
                return root.right
            if root.left != None and root.right != None:
                cur = root.right
                while cur.left != None:
                    cur = cur.left
                cur.left = root.left
                return root.right
            if root.left == None and root.right == None:
                return None
        if key > root.val:
            root.right = self.deleteNode(root.right,key)
        if key < root.val:
            root.left = self.deleteNode(root.left,key)
        return root

总结

递归真的要命,真救了命了。。。。。。
好多点其实还是有点模糊,我估计再做几道应该能扫清一些障碍。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值