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

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

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

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

示例 1:

  • 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
  • 输出: 6
  • 解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

  • 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
  • 输出: 2
  • 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

思路:

一开始只能想到普通的二叉树搜索方法套用过来,主要是没能想通从根节点往下搜索,第一次遍历到的处于两点值之间的节点值就是最近公共祖先。以下资料助于理解:

路径唯一性

    • 在BST中,从根节点到任何节点的路径是唯一的。这意味着任何两个节点的路径在某个点上会分叉,这个分叉点就是它们的最近公共祖先。

不可能再低

    • 如果你继续向下遍历,无论是向左还是向右,你都会离开其中一个节点的路径,因此不可能找到更低的公共祖先。

代码实现如下:

# 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

       

        cur = root

        while cur:

            if cur.val < p.val and cur.val < q.val:

                cur = cur.right

            elif cur. val > p.val and cur.val > q.val:

                cur = cur.left

            else:

                return cur

        return None

规范代码:

递归法:

class Solution:

    def traversal(self, cur, p, q):

        if cur is None:

            return cur

                                                        # 中

        if cur.val > p.val and cur.val > q.val:           # 左

            left = self.traversal(cur.left, p, q)

            if left is not None:

                return left



        if cur.val < p.val and cur.val < q.val:           # 右

            right = self.traversal(cur.right, p, q)

            if right is not None:

                return right



        return cur



    def lowestCommonAncestor(self, root, p, q):

        return self.traversal(root, p, q)

迭代法:

class Solution:

    def lowestCommonAncestor(self, root, p, q):

        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.二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。

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

提示:

  • 给定的树上的节点数介于 0 和 10^4 之间
  • 每个节点都有一个唯一整数值,取值范围从 0 到 10^8
  • -10^8 <= val <= 10^8
  • 新值和原始二叉搜索树中的任意节点值都不同

思路:

使用二叉搜索树的特性,搜索该节点应该在的位置(事实上搜索树中没有这个节点),在遍历到叶子节点后,直接将插入节点插入在该叶子节点下作为新的叶子节点即可。

# 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:

            root = TreeNode(val)

            return root

       

        pre = None

        cur = root

        while cur:

            pre = cur

            if val > cur.val:

                cur = cur.right

            elif val < cur.val:

                cur = cur.left

           

        if val > pre.val:

            pre.right = TreeNode(val)

        else:

            pre.left = TreeNode(val)

        return root

规范代码:

递归法:

class Solution:

    def __init__(self):

        self.parent = None



    def traversal(self, cur, val):

        if cur is None:

            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, val):

        if root is None:  # 如果根节点为空,创建新节点作为根节点并返回

            node = TreeNode(val)

            return node



        cur = root

        parent = root  # 记录上一个节点,用于连接新节点

        while cur is not None:

            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.删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 $O(h)$,h 为树的高度。

示例:

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

思路:

删除需要分多种情况:

  1. 找不到该节点,直接返回
  2. 删除的是根节点:
    1. 右子树存在:直接将左子树作为这个【右子树的最小节点】的左子树即可
    2. 右子树不存在:直接将左子树作为新的树
  3. 找到对应的节点且不是根节点:以该节点为根节点的子树,对这棵子树执行以上第二步删除根节点的操作,使得子树变为返回的新子树。

代码实现如下:

# 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

       

        pre = None

        cur = root

        while cur:

            if key > cur.val:

                pre = cur

                cur = cur.right

            elif key < cur.val:

                pre = cur

                cur = cur.left

            else:

                if not pre:     # 删除根节点的情况

                    if cur.right:

                        newroot = cur.right

                        while newroot.left:

                            newroot = newroot.left

                        #root.val = newroot.val

                        #newroot = newroot.right

                        #return root

                        #原本的做法是想将右子树的最小节点作为新的根节点,然后将该节点删除,发现过程要另外考虑的东西多很多,看了解析后豁然开朗,直接将左子树作为这个【右子树的最小节点】的左子树即可

                        newroot.left = root.left

                        return root.right

                    else:

                        return root.left

                else:           # 删除其他节点的情况

                    #cur = self.deleteNode(cur, key)

                    if cur.val < pre.val:

                        pre.left = self.deleteNode(cur, key)

                    else:

                        pre.right = self.deleteNode(cur, key)

                    break

       

        return root

规范代码:

递归法:

class Solution:

    def deleteNode(self, root, key):

        if root is None:

            return root

        if root.val == key:

            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

                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

迭代法:

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

第二十二天的算法训练营主要涵盖了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、付费专栏及课程。

余额充值