Day21 算法学习|二叉树07 继续递归,难度提升

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

问题描述

给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值。

解题思路

  1. 我们需要了解二叉搜索树(BST)的一个关键特性:对 BST 进行中序遍历将得到一个递增的序列。这意味着最小的绝对差将存在于两个相邻的元素之间。
  2. 由于最小的绝对差在相邻的元素之间,我们在进行中序遍历的同时,比较当前节点和前一个节点的差值,更新最小差值。
  3. 我们使用一个全局变量 prev 来记录前一个节点,min_diff 来记录当前的最小差值。在遍历的过程中,我们更新这两个变量。

代码

  • 定义二叉树节点和初始化全局变量
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def __init__(self):
        self.prev = -1
        self.min_diff = float('inf')
  • 中序遍历和更新最小差值
    def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
        if root is None:
            return self.min_diff

        self.getMinimumDifference(root.left)

        if self.prev != -1:
            self.min_diff = min(self.min_diff, root.val - self.prev)
        self.prev = root.val

        self.getMinimumDifference(root.right)
        
        return self.min_diff

  • 利用中序递增,结合数组
class Solution:
    def __init__(self):
        self.vec = []

    def traversal(self, root):
        if root is None:
            return
        self.traversal(root.left)
        self.vec.append(root.val)  # 将二叉搜索树转换为有序数组
        self.traversal(root.right)

    def getMinimumDifference(self, root):
        self.vec = []
        self.traversal(root)
        if len(self.vec) < 2:
            return 0
        result = float('inf')
        for i in range(1, len(self.vec)):
            # 统计有序数组的最小差值
            result = min(result, self.vec[i] - self.vec[i - 1])
        return result

  • 迭代法
class Solution:
    def getMinimumDifference(self, root):
        stack = []
        cur = root
        pre = None
        result = float('inf')

        while cur is not None or len(stack) > 0:
            if cur is not None:
                stack.append(cur)  # 将访问的节点放进栈
                cur = cur.left  # 左
            else:
                cur = stack.pop()
                if pre is not None:  # 中
                    result = min(result, cur.val - pre.val)
                pre = cur
                cur = cur.right  # 右

        return result

复杂度分析(递归)

  • 时间复杂度 O(N)

  • 其中 N 是 BST 中节点的数量,因为我们需要遍历整棵树。
  • O(H),其中 H 是 BST 的高度

也就是递归栈的深度。在最坏的情况下,树可能完全倾斜,递归深度等于树的节点数量。

  • 空间复杂度 O(H),其中 H 是 BST 的高度

    也就是递归栈的深度。在最坏的情况下,树可能完全倾斜,递归深度等于树的节点数量。

题目 501.二叉搜索树中的众数

问题描述

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
在这里插入图片描述

解题思路

  • 中序遍历:使用中序遍历的方式遍历二叉搜索树。首先遍历左子树,然后访问当前节点,最后遍历右子树。
  • 使用全局变量:为了在递归过程中共享状态,使用全局变量来记录上一个节点的值和当前的最小差值。
  • 初始化变量:在开始遍历之前,初始化上一个节点的值为 None,并将最小差值设置为一个较大的初始值(如正无穷)。
  • 更新最小差值:对于当前节点,计算它与上一个节点值的差值,并将其与当前最小差值进行比较。如果小于当前最小差值,则更新最小差值。
  • 递归遍历左右子树:在递归调用中,按照中序遍历的顺序遍历左子树和右子树。

代码

  • 递归法(版本一)利用字典
from collections import defaultdict

class Solution:
    def searchBST(self, cur, freq_map):
        if cur is None:
            return
        freq_map[cur.val] += 1  # 统计元素频率
        self.searchBST(cur.left, freq_map)
        self.searchBST(cur.right, freq_map)

    def findMode(self, root):
        freq_map = defaultdict(int)  # key:元素,value:出现频率
        result = []
        if root is None:
            return result
        self.searchBST(root, freq_map)
        max_freq = max(freq_map.values())
        for key, freq in freq_map.items():
            if freq == max_freq:
                result.append(key)
        return result


  • 递归法(版本二)利用二叉搜索树性质
class Solution:
    def __init__(self):
        self.maxCount = 0  # 最大频率
        self.count = 0  # 统计频率
        self.pre = None
        self.result = []

    def searchBST(self, cur):
        if cur is None:
            return

        self.searchBST(cur.left)  # 左
        # 中
        if self.pre is None:  # 第一个节点
            self.count = 1
        elif self.pre.val == cur.val:  # 与前一个节点数值相同
            self.count += 1
        else:  # 与前一个节点数值不同
            self.count = 1
        self.pre = cur  # 更新上一个节点

        if self.count == self.maxCount:  # 如果与最大值频率相同,放进result中
            self.result.append(cur.val)

        if self.count > self.maxCount:  # 如果计数大于最大值频率
            self.maxCount = self.count  # 更新最大频率
            self.result = [cur.val]  # 很关键的一步,不要忘记清空result,之前result里的元素都失效了

        self.searchBST(cur.right)  # 右
        return

    def findMode(self, root):
        self.count = 0
        self.maxCount = 0
        self.pre = None  # 记录前一个节点
        self.result = []

        self.searchBST(root)
        return self.result

  • 迭代法

class Solution:
    def findMode(self, root):
        st = []
        cur = root
        pre = None
        maxCount = 0  # 最大频率
        count = 0  # 统计频率
        result = []

        while cur is not None or st:
            if cur is not None:  # 指针来访问节点,访问到最底层
                st.append(cur)  # 将访问的节点放进栈
                cur = cur.left  # 左
            else:
                cur = st.pop()
                if pre is None:  # 第一个节点
                    count = 1
                elif pre.val == cur.val:  # 与前一个节点数值相同
                    count += 1
                else:  # 与前一个节点数值不同
                    count = 1

                if count == maxCount:  # 如果和最大值相同,放进result中
                    result.append(cur.val)

                if count > maxCount:  # 如果计数大于最大值频率
                    maxCount = count  # 更新最大频率
                    result = [cur.val]  # 很关键的一步,不要忘记清空result,之前result里的元素都失效了

                pre = cur
                cur = cur.right  # 右

        return result

复杂度分析

  • 时间复杂度 O(N)

  • 空间复杂度 O(H)

  • 其中 H 是二叉树的高度。对于平衡二叉树,空间复杂度为 O(logN)。但对于不平衡的二叉树,最坏情况下空间复杂度为 O(N)。

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

在这里插入图片描述

问题描述

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

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

解题思路

  • 从根节点开始搜索。
  • 如果根节点为空,或者根节点就是要查找的其
    中一个节点,我们返回根节点。
  • 我们对左子树和右子树进行递归搜索。如果左右两侧都找到了,那么根节点就是最近公共祖先。
  • 如果左侧没有找到,那么最近公共祖先在右侧;反之亦然。
    如果左右两侧都没找到,那么最近公共祖先不存在。
    -遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。

注意!

  • 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
  • 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
  • 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。

递归过程

  1. 从根节点 3 开始执行 lowestCommonAncestor(3, 7, 4)

    • 检查节点 3 是否等于节点 7 或节点 4。这不是真的,所以我们需要继续递归。
  2. 在左子树中递归地找节点 7 和节点 4 的最近公共祖先,即执行 lowestCommonAncestor(5, 7, 4)

    • 在节点 5 的左子树中(即节点 6),我们没有找到节点 7 或节点 4,所以返回 None。
    • 在节点 5 的右子树中(即节点 2),我们找到了节点 7 和节点 4,它们的最近公共祖先是节点 2,所以返回节点 2。
    • 因此,lowestCommonAncestor(5, 7, 4) 在左子树中返回 None,右子树中返回节点 2,所以最终返回节点 2。
  3. 回到 lowestCommonAncestor(3, 7, 4),我们在左子树中找到了最近公共祖先,即节点 2。

  4. 接着,我们在右子树中找节点 7 和节点 4 的最近公共祖先,即执行 lowestCommonAncestor(1, 7, 4)

    • 在节点 1 的左子树(即节点 0)和右子树(即节点 8)中,我们都没有找到节点 7 或节点 4,所以都返回 None。
    • 因此,lowestCommonAncestor(1, 7, 4) 在左子树和右子树中都返回 None,所以最终返回 None。
  5. 最后,在 lowestCommonAncestor(3, 7, 4) 中,左子树的返回值是节点 2,右子树的返回值是 None,所以返回左子树的返回值,即节点 2。

所以,节点 7 和节点 4 的最近公共祖先是节点 2。

代码

  • 递归
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 如果根节点为空,返回None
        if root is None:
            return None
        
        # 如果根节点等于p或q,说明根节点是它们的公共祖先
        if root == p or root == q:
            return root
        
        # 在左子树中寻找p和q的最近公共祖先
        left = self.lowestCommonAncestor(root.left, p, q)
        
        # 在右子树中寻找p和q的最近公共祖先
        right = self.lowestCommonAncestor(root.right, p, q)
        
        # 如果p和q分别在根节点的左右子树中,那么根节点就是它们的最近公共祖先
        if left is not None and right is not None:
            return root
        # 如果只有右子树中找到了公共祖先,返回右子树中的公共祖先
        elif left is None and right is not None:
            return right
        # 如果只有左子树中找到了公共祖先,返回左子树中的公共祖先
        elif left is not None and right is None:
            return left
        # 如果左右子树中都没有找到公共祖先,返回None
        else:
            return None

  • 递归法(版本二)精简

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if root == q or root == p or root is None:
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if left is not None and right is not None:
            return root

        if left is None:
            return right
        return left

复杂度分析

  • 时间复杂度 O(N)

  • 空间复杂度 O(log(N)),O(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值