代码随想录算法训练营Day21 | 530. 二叉搜索树的最小绝对差 | 501. 二叉搜索树中的众数 | 236. 二叉树的最近公共祖先

530. 二叉搜索树的最小绝对差

题目链接 | 解题思路

直接对 BST 进行中序遍历,得到的结果就是一个递增的有序数组,再求解就非常容易了。

但其实可以利用 BST 的有序性,直接通过一次遍历解决问题。这里用到了之前的双指针

普通递归

记录当前节点的上下限(根据之前的父节点方向记录)。

class Solution:
    def limitMinDiff(self, node: Optional[TreeNode], lower: int, upper: int) -> int:
        # node will not be None
        curr_records = [upper - node.val, node.val - lower]
        if node.left != None:
            curr_records.append(node.val - node.left.val)
            curr_records.append(self.limitMinDiff(node.left, lower, node.val))
        if node.right != None:
            curr_records.append(node.right.val - node.val)
            curr_records.append(self.limitMinDiff(node.right, node.val, upper))
        return min(curr_records)

    def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
        if root == None:
            return 0
        return self.limitMinDiff(root, float("-inf"), float("inf"))

双指针

看到 BST 的第一反应一定是中序遍历,最大程度利用其单调性,同时通过 pre 指针来记录相邻的前一个节点,进行相关的操作。

class Solution:
    def __init__(self):
        self.minDiff = float("inf")
        self.pre = None

    def limitMinDiff(self, node: Optional[TreeNode]) -> None:
        if node == None:
            return
        
        # left
        self.limitMinDiff(node.left)

        # middle
        if self.pre != None:                
            if self.minDiff > node.val - self.pre.val:
                self.minDiff = node.val - self.pre.val
        self.pre = node

        # right
        self.limitMinDiff(node.right)


    def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
        if root == None:
            return 0
        self.limitMinDiff(root)
        return self.minDiff

501. 二叉搜索树中的众数

题目链接 | 解题思路

暴力算法非常直接,如果是一棵普通的树,用任意方式遍历整棵树然后生成 val: count 的 mapping,然后统计 count 最高的元素即可。但是暴力算法需要排序, O ( n log ⁡ n ) O(n\log{n}) O(nlogn) 的复杂度。

题目中给出的 BST 条件规定了单调性,众数又代表着只需要求最大值即可。这样的条件让我们能够一次遍历( O ( n ) O(n) O(n) 的复杂度)解决问题,思路与之前的单调数组一模一样。另外本题中 BST 允许重复元素。

BST 的所有题型,都要优先考虑中序遍历!最优利用 BST 的单调性。
同时考虑双指针!因为双指针能记录相邻两个元素的值,一个指向当前节点,一个指向之前的节点,能够很好解决 BST 中隔(很多)层记录上下限的问题。

class Solution:
    def __init__(self):
        self.count = 0
        self.maxCount = 0
        self.maxRecords = []
        self.pre = None


    def inorder_traverse(self, curr_node: Optional[TreeNode]) -> None:
        if curr_node == None:
            return
        
        # left node
        self.inorder_traverse(curr_node.left)
        if self.pre == None or self.pre.val != curr_node.val:
            self.count = 1
        else:
            self.count += 1

        self.pre = curr_node
        
        # middle
        if self.count == self.maxCount:
            self.maxRecords.append(curr_node.val)
        if self.count > self.maxCount:
            self.maxCount = self.count
            self.maxRecords = [curr_node.val]
        
        # right
        self.inorder_traverse(curr_node.right)
        

    def findMode(self, root: Optional[TreeNode]) -> List[int]:
        self.inorder_traverse(root)
        return self.maxRecords

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

题目链接 | 解题思路

查找两个节点的最近公共祖先,直觉上应该是一个自下而上的查找顺序(回溯!),所以应该使用后序遍历:根据子树的信息处理当前的中节点。

这里递归的思路是,当遇到了一个目标节点(p 或者 q),就返回该节点,否则返回 None。这一过程不断向上传递,直到有一个节点的左子树和右子树都显示遇到过目标节点,此时该节点即是两个目标节点的最近公共祖先。这么做的前提是二叉树中没有重复的元素。
另一种情况是,一个目标节点 p 是另一个目标节点 q 的祖先。这种情况下,之前传递的 q 节点会被 p 代替,p 会不断回溯到根节点,同样能返回正确答案。

在这里插入图片描述

注意,函数无需区分遇到的是 p 还是 q,重要的是遇到了目标节点。不断向上传递遇到目标节点的节点实际上是在进行回溯。

回溯解法

这种解法依然要遍历整棵树:即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值做逻辑判断。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root == None:        # not meet the target, but at the end 
            return None
        if root == p or root == q:      # meet a target
            return root

        left_result = self.lowestCommonAncestor(root.left, p, q)
        right_result = self.lowestCommonAncestor(root.right, p, q)
        if left_result != None and right_result != None:
            return root
        if left_result != None and right_result == None:
            return left_result
        if left_result == None and right_result != None:
            return right_result

暴力解法

求两个节点的最近公共祖先,很自然的想法是找到两个节点的各自的祖先们,然后从根节点开始同时遍历,找到最后一个公共祖先即可。
这样的解法中使用的是自上而下的前序遍历。

class Solution:
    def findAncestors(self, root: Optional[TreeNode], ancestors: List[TreeNode], node: TreeNode) -> List[TreeNode]:
        ancestors.append(root)
        if root == node:
            return ancestors
        if root.left == None and root.right == None:
            return []
        
        result = []
        if root.left != None:
            result.extend(self.findAncestors(root.left, ancestors, node))
            ancestors.pop()
        if root.right != None:
            result.extend(self.findAncestors(root.right, ancestors, node))
            ancestors.pop()
        return result
        

    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        p_ancestors = self.findAncestors(root, [], p)
        q_ancestors = self.findAncestors(root, [], q)

        i = 0
        while (i < len(p_ancestors) and i < len(q_ancestors)):
            if p_ancestors[i] != q_ancestors[i]:
                return p_ancestors[i-1]
            i += 1
        if i == len(p_ancestors):
            return p_ancestors[-1]
        else:
            return q_ancestors[-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值