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

代码随想录系列文章目录

二叉树篇-二叉搜索树



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

题目链接

和二叉树的最近祖先那道题的差别

236.二叉树的最近公共祖先,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root: return None
        if root == q or root == p: return root
        
        left = self.lowestCommonAncestor(root.left, p, q)      #左
        right = self.lowestCommonAncestor(root.right, p, q)    #右
                                                               #中
        if left != None and right != None:       
            return root                             #左子树 右子树 发现pq
        elif left == None and right != None:
            return right                            #返回有发现qp 那个子树,即不空的子树
        elif left != None and right == None:
            return left
        else: return None

本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
其实只要从上到下遍历的时候,cur节点是数值在[p, q]区间中说明该节点cur就是最近公共祖先了。在这里插入图片描述
为什么呢?如图,此时 5 在 p 和 q的中间,如果往左子树走了,右边的q会被失掉,反之亦然

普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。

那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。
在这里插入图片描述
可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!

dfs

# 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 p.val < root.val and q.val < root.val:  #pq < root.val 在左子树里找
            left = self.lowestCommonAncestor(root.left, p, q)  #要把公共祖先找到的,返回上来,所以要left接住
            if left: return left
        if p.val > root.val and q.val > root.val:  #pq > root.val  在右子树里找
            right = self.lowestCommonAncestor(root.right, p, q)
            if right: return right
        return root   #剩下的情况就是root在pq之间了

迭代

class Solution:
    """二叉搜索树的最近公共祖先 迭代法"""

    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while True:
            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.二叉搜索树中的插入操作

题目链接

其实这道题目其实是一道简单题目,但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人,瞬间感觉题目复杂了很多。

其实可以不考虑题目中提示所说的改变树的结构的插入方式。就插在叶子结点就行了,不改变二叉搜索树的结构。

思路就变成了,只要遍历二叉搜索树,找到空节点 插入元素就可以了,那么这道题其实就简单了。 再分解一下,找到叶子的位置,创建val == 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 root == None:                 #找到叶子结点的位置
            node = TreeNode(val)         #创建新节点
            return node                  #有返回值,可以利用返回值完成新加入的节点与其父节点的赋值操作
        if root.val < val:
            root.right = self.insertIntoBST(root.right, val)
        if root.val > val:
            root.left = self.insertIntoBST(root.left, val)

        return root

就是root传进来,根据二叉搜索树的 left<root<right 的这个关系,一层层往下找,找到应该插在的叶子结点位置,然后实现插的操作,然后返回插完之后的树的根

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

题目链接
这道题不是很难,就是要分情况去动这个树的的结构,各种情况都写进代码里了

# 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 root == None: return None
        if root.val == key:
            #第一种,要删除的结点是叶子结点,左空 右空
            if not root.left and not root.right:
                return None  #为什么return None, 因为删除的是叶子,叶子没有后继
            #第二种,要删除结点的左子不空,右子空  返回左子
            elif root.left and not root.right:
                return root.left
            #第三种,要删除的结点的左子空, 右子不空,返回右子
            elif not root.left and root.right:
                return root.right
            #第四种,要删除的结点的左子不空,右子不空,要把左子树加到右子树最左边的一个结点上
            else:
                curr = root.right
                while curr.left != None:  #去找,要删除的结点 右子树的 最左边的一个结点
                    curr = curr.left      #此时, curr指向  要删除的结点的  右子树的  最左的孩子

                curr.left = root.left     #要删除的左子树嫁接到右子树的最左边的一个孩子
                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 {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        if (root->val == key) {
            if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用
                return root->left;
            }
            TreeNode *cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二算法训练营主要涵盖了Leetcode题目的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值