leetcode
leetcode 451 ~ 500
二叉搜索树中的众数

二叉搜索树中的众数

难度:

标签:

题目描述

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

 

示例 1:

输入:root = [1,null,2,2]
输出:[2]

示例 2:

输入:root = [0]
输出:[0]

 

提示:

  • 树中节点的数目在范围 [1, 104]
  • -105 <= Node.val <= 105

 

进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

代码结果

运行时间: 56 ms, 内存: 19.1 MB


/*
 * 题目思路:
 * 1. 使用Java Stream实现,我们可以先进行中序遍历收集所有元素,然后使用Collectors.groupingBy统计频率。
 * 2. 通过Collectors.maxBy找到最大频率,然后筛选出所有具有最大频率的元素。
 */
 
import java.util.*;
import java.util.stream.*;
 
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
 
public class SolutionStream {
    public int[] findMode(TreeNode root) {
        List<Integer> values = new ArrayList<>();
        inOrder(root, values);
        Map<Integer, Long> frequencyMap = values.stream()
            .collect(Collectors.groupingBy(x -> x, Collectors.counting()));
        long maxFrequency = frequencyMap.values().stream()
            .max(Long::compare).orElse(0L);
        return frequencyMap.entrySet().stream()
            .filter(entry -> entry.getValue() == maxFrequency)
            .mapToInt(Map.Entry::getKey)
            .toArray();
    }
    
    private void inOrder(TreeNode node, List<Integer> values) {
        if (node == null) return;
        inOrder(node.left, values);
        values.add(node.val);
        inOrder(node.right, values);
    }
}
 

解释

方法:

这个题解使用中序遍历的方式遍历二叉搜索树,同时使用一些变量来记录当前节点的值、出现次数、最大出现次数等信息。在遍历过程中,通过比较当前节点值与上一个节点值是否相同,来判断当前节点是否为众数。如果当前节点出现次数等于最大出现次数,将当前节点值加入结果列表;如果大于最大出现次数,则更新结果列表为只包含当前节点值,并更新最大出现次数。遍历完整棵树后,结果列表中就是所有的众数。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么选择中序遍历来处理这个问题,而不是前序或后序遍历?
中序遍历是因为二叉搜索树的特性,中序遍历可以按照递增的顺序访问所有节点。这样在遍历时能够方便比较当前节点的值与前一个访问的节点的值,进而统计每个值出现的次数。如果使用前序或后序遍历,节点的访问顺序将不再保证递增,这将增加统计众数的复杂度,因为需要额外的数据结构来跟踪和比较每个值的出现频率。
🦆
在算法中,如何确保在更新最大出现次数`maxTimes`时,能正确地从结果列表`res`中移除旧的众数?
在算法中,每当发现当前节点值的出现次数`curTimes`超过了之前的最大出现次数`maxTimes`时,算法会更新`maxTimes`为新的最高出现次数,并且将结果列表`res`设置为只包含当前节点的值。这是通过将`res`赋值为一个只包含当前节点值的新列表来实现的,从而自动移除了之前的众数。
🦆
当树中只有一个节点时,算法的表现是怎样的?是否直接返回该单个节点作为众数,还是需要遍历整棵树?
当树中只有一个节点时,算法仍然会遍历这个节点。在这种情况下,由于只有一个节点,它自然成为出现次数最多的节点。算法会在访问这个节点时将其添加到结果列表`res`中,并将`maxTimes`更新为1。因此,算法将返回包含这个单一节点的列表作为众数。即使树只有一个节点,中序遍历仍然会正常执行这个过程。
🦆
题解中提到使用栈来模拟递归调用,这种方法与直接使用系统调用栈的递归相比,有什么优势或劣势?
使用栈来模拟递归调用的优势在于对递归过程的控制更为直观和灵活,特别是在需要进行细致操作的情况下,如迭代中的状态管理和变量更新。此外,这种方法避免了递归可能导致的栈溢出问题,尤其是在处理非常深的树时。然而,这种方法的劣势是代码可能更复杂,难于理解和维护,同时手动管理栈可能比自然的递归调用效率略低。

相关问题

验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

 

示例 1:

输入:root = [2,1,3]
输出:true

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

 

提示:

  • 树中节点数目范围在[1, 104]
  • -231 <= Node.val <= 231 - 1