当前位置: 首页 > news >正文

501. 二叉搜索树中的众数

题目链接:

501. 二叉搜索树中的众数 - 力扣(LeetCode)

题目:

解题思路:

递归,中序遍历(将搜索树结构变成有序数组进行处理),涉设置一个全局集合变量和全局计数器和最大数值计数器,当遍历到的元素值与之前相等就count++,若不等就去判断是否大于之前的元素,还是等于之前的元素,若等于之前元素的数量,就直接加入集合就行,如大于之前元素的数量,先把集合清空,再将当前元素加入集合中,最后遍历一遍将集合变成数即可

代码:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*//*
*Stack Integer ArrayList String StringBuffer peek
*Collections imports LinkedList offer return
*empty polls offerLast pollFirst isEmpty
*List Deque append length HashMap null
*return
*/
class Solution {List<Integer> res;int count;int maxCount;TreeNode pre;public int[] findMode(TreeNode root) {count=0;maxCount=0;res=new ArrayList<>();find(root);if(pre!=null){check();}int [] arr=new int[res.size()];for(int i=0;i<arr.length;i++){arr[i]=res.get(i);}return arr;}public void find(TreeNode root){if(root==null) return ;find(root.left);if(pre==null||pre.val!=root.val){//这里用或那pre.val还能使用吗if(pre!=null){check();}count=1;pre=root;}else{count++;}find(root.right);}public void check(){if(count==maxCount){res.add(pre.val);}else if(count>maxCount){res.clear();res.add(pre.val);maxCount=count;}}
}


深入解析二叉搜索树众数查找算法:逻辑与实现细节
在二叉树相关算法中,寻找众数(出现次数最多的元素)是一个经典问题。尤其对于二叉搜索树(BST),我们可以利用其特殊性质设计出高效的解决方案。本文将详细解析一段基于中序遍历的 BST 众数查找代码,探讨其设计思路、实现细节及注意事项。
算法背景与思路
二叉搜索树的重要特性是:中序遍历结果为有序序列。这一特性让我们可以将 BST 视为一个有序数组,相同的元素会连续出现,这为统计元素出现次数提供了极大便利。
我们的核心思路是:
通过中序遍历遍历整个 BST,获得有序序列
实时统计当前元素的出现次数
维护一个最大值计数器,记录已出现的最大频次
根据当前元素频次与最大频次的关系,更新结果集
这种方法无需额外空间存储整个序列,只需 O (1) 的额外空间(不包括结果存储),时间复杂度为 O (n)(n 为节点总数)。
代码结构解析
我们先来看完整代码结构,以便建立整体认知:
java
运行
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> res;      // 存储众数结果
    int count;              // 当前元素出现次数计数器
    int maxCount;           // 最大出现次数记录
    TreeNode pre;           // 前一个节点的引用
    
    public int[] findMode(TreeNode root) {
        // 初始化变量
        count = 0;
        maxCount = 0;
        res = new ArrayList<>();
        // 执行查找逻辑
        find(root);
        // 处理最后一个节点
        if(pre != null){
            check();
        }
        // 转换结果为数组并返回
        int [] arr = new int[res.size()];
        for(int i = 0; i < arr.length; i++){
            arr[i] = res.get(i);
        }
        return arr;
    }
    
    // 中序遍历核心逻辑
    public void find(TreeNode root){
        if(root == null) return ;
        find(root.left);  // 左
        
        // 中:处理当前节点
        if(pre == null || pre.val != root.val){
            if(pre != null){
                check();
            }
            count = 1;
            pre = root;
        }else{
            count++;
        }
        
        find(root.right); // 右
    }
    
    // 检查并更新结果集
    public void check(){
        if(count == maxCount){
            res.add(pre.val);
        }else if(count > maxCount){
            res.clear();
            res.add(pre.val);
            maxCount = count;
        }
    }
}
代码主要包含三个部分:
findMode():主方法,负责初始化、触发遍历和结果转换
find():递归中序遍历方法,实现核心计数逻辑
check():根据计数结果更新众数集合
核心变量解析
让我们详细了解几个关键变量的作用:
res:List<Integer>类型,用于存储所有众数。由于可能存在多个频次相同的众数,需要用列表存储。
count:当前元素的出现次数计数器。每当遇到相同元素时递增,遇到新元素时重置为 1。
maxCount:记录遍历过程中遇到的最大频次。用于判断当前元素是否有资格成为众数。
pre:指向 "前一个节点" 的引用,用于与当前节点比较值是否相同,是实现连续计数的关键。
这些变量被定义为成员变量而非局部变量,避免了递归过程中的参数传递开销,同时保证了状态在递归调用间的连续性。
关键逻辑详解
1. 中序遍历的实现
java
运行
public void find(TreeNode root){
    if(root == null) return ;
    find(root.left);  // 左子树遍历
    
    // 处理当前节点(中序位置)
    // ...
    
    find(root.right); // 右子树遍历
}
这是典型的递归式中序遍历实现,遵循 "左 - 中 - 右" 的访问顺序。正是这种顺序保证了我们按从小到大的顺序访问 BST 中的元素。
2. 元素计数逻辑
计数逻辑是整个算法的核心,位于中序遍历的 "中" 位置:
java
运行
if(pre == null || pre.val != root.val){
    if(pre != null){
        check();
    }
    count = 1;
    pre = root;
}else{
    count++;
}
这段代码的作用是:判断当前节点与前一个节点是否相同,以决定是开始新计数还是延续当前计数。
关于 "或" 操作的疑问解析
有读者可能会问:if(pre == null || pre.val != root.val)中使用了 "或" 操作,当pre == null为真时,pre.val不会被执行,这是为什么?
这是 Java 的短路逻辑特性:当使用||(逻辑或)时,如果第一个条件为true,则不会执行第二个条件。因此当pre为null时,pre.val不会被访问,也就不会出现空指针异常。
这种写法简洁地处理了两种情况:
pre == null:表示这是遍历的第一个节点
pre.val != root.val:表示遇到了新的元素
计数逻辑的执行流程
当遇到新元素(或第一个元素)时:
如果不是第一个元素(pre != null),则检查上一个元素的计数情况(调用check())
重置计数器count = 1
更新pre指向当前节点
当遇到相同元素时:
计数器count递增(count++)
不更新pre(仍指向前一个相同元素)
3. 结果集更新逻辑
check()方法负责根据当前计数更新结果集:
java
运行
public void check(){
    if(count == maxCount){
        // 当前元素频次与最大频次相同,加入结果集
        res.add(pre.val);
    }else if(count > maxCount){
        // 当前元素频次更高,清空原有结果,更新最大频次
        res.clear();
        res.add(pre.val);
        maxCount = count;
    }
}
这个方法的逻辑很清晰:
当当前元素频次等于最大频次:加入结果集
当当前元素频次大于最大频次:清空原有结果(因为它们不再是众数),添加当前元素,更新最大频次
4. 最后一个元素的处理
在findMode()主方法中,有一段特殊处理:
java
运行
// 处理最后一个节点
if(pre != null){
    check();
}
为什么需要这段代码?因为在find()方法中,我们只在遇到新元素时才会检查上一个元素。而遍历的最后一个元素没有 "下一个元素" 来触发检查,所以需要在遍历结束后专门处理。
这是一个容易被忽略的边界情况,缺少这段代码会导致最后一组元素的计数无法被正确统计。
5. 结果转换
最后,我们需要将List<Integer>转换为题目要求的int[]类型:
java
运行
int [] arr = new int[res.size()];
for(int i = 0; i < arr.length; i++){
    arr[i] = res.get(i);
}
return arr;
这是 Java 集合与基本类型数组之间的常规转换操作。
算法执行流程示例
为了更好地理解算法执行过程,我们用一个简单的 BST 示例来模拟:
假设 BST 结构如下(中序遍历结果:1, 2, 2, 3, 3, 3):
plaintext
    2
   / \
  1   3
     /
    3
   /
  3
执行流程:
遍历到 1(第一个节点):
pre 为 null,count=1,pre 指向 1
遍历到 2:
pre.val (1) != 2,调用 check () 检查 1(count=1)
maxCount 为 0,因此 res 添加 1,maxCount=1
count 重置为 1,pre 指向 2
遍历到 2(第二个 2):
与 pre.val 相同,count=2
遍历到 3(第一个 3):
pre.val (2) != 3,调用 check () 检查 2(count=2)
2 > maxCount (1),清空 res,添加 2,maxCount=2
count 重置为 1,pre 指向 3
遍历到 3(第二个 3):
与 pre.val 相同,count=2
遍历到 3(第三个 3):
与 pre.val 相同,count=3
遍历结束,调用 check () 检查最后一个元素 3(count=3):
3 > maxCount (2),清空 res,添加 3,maxCount=3
最终结果:[3],正确反映了 3 是出现次数最多的元素。
算法优化点与特性
空间效率:算法只使用了常数级别的额外空间(不包括结果存储),优于使用哈希表计数的 O (n) 空间复杂度方法。
时间效率:只需一次遍历(O (n) 时间)即可完成统计,无需二次遍历。
实时更新:在遍历过程中实时更新结果集,避免了存储整个序列的需求。
边界处理:专门处理了第一个元素和最后一个元素的特殊情况,确保计数完整。
可能的改进方向
虽然当前算法已经很高效,但仍有一些可改进之处:
非递归实现:对于极深的树,递归可能导致栈溢出,可以改为迭代式中序遍历。
提前终止:如果已知树中存在超过一半的元素是同一个值(绝对众数),可以添加提前终止逻辑。
泛型支持:可以扩展为支持任意可比类型的 BST,而不仅仅是整数。
总结
本文详细解析了基于中序遍历的 BST 众数查找算法,该算法巧妙利用了 BST 中序遍历的有序性,实现了高效的众数查找。核心思想是通过一次遍历实时统计元素出现次数,并根据计数结果动态更新众数集合。
算法的关键在于:
正确维护前一个节点的引用以判断元素是否连续
在元素切换时及时检查上一个元素的计数情况
专门处理最后一个元素以避免遗漏
理解这个算法不仅能帮助我们解决众数查找问题,更能加深对 BST 特性和中序遍历应用的理解,为解决其他树相关问题提供思路。

总结:
 

本文详细解析了二叉搜索树(BST)中众数查找的高效算法。该算法利用BST中序遍历的有序特性,通过一次遍历实时统计元素出现次数:使用count记录当前元素频次,maxCount跟踪最大频次,pre节点比较相邻元素值。当遇到不同值时调用check()方法更新结果集,需特别注意处理首尾节点的边界情况。该方案仅需O(n)时间复杂度和O(1)额外空间(不含结果存储),相比哈希表方法更节省空间。关键点包括中序遍历的有序性处理、计数逻辑的精确控制以及结果集的动态更新,为树结构统计问题提供了典型解决思路。

http://www.dtcms.com/a/392662.html

相关文章:

  • Go面试题及详细答案120题(81-100)
  • 在跨平台C++项目中条件化使用Intel MKL与LAPACK/BLAS进行矩阵计算
  • 知芽AI(paperxx)写作:开题报告写作宝典
  • c++26新功能—模板参数中的概念与变量模板
  • Linux服务器上安装配置GitLab的步骤
  • Netty原理介绍
  • 【已解决】在windows系统安装fasttext库,解决安装fasttext报错问题
  • 从“free”到“free_s”:内存释放更安全——free_s函数深度解析与free全方位对比
  • 【LeetCode 每日一题】1733. 需要教语言的最少人数
  • 多模态知识图谱
  • 基于python spark的航空数据分析系统的设计与实现
  • 【每日一问】运放单电源供电和双电源供电的区别是什么?
  • LeetCode算法领域的经典题目之“三数之和”和“滑动窗口最大值”问题
  • SpringCloudConfig:分布式配置中心
  • Go变量与类型简明指南
  • 每天学习一个统计检验方法--曼-惠特尼U检验(以噩梦障碍中的心跳诱发电位研究为例)
  • linux创建服务器
  • 线性代数基础 | 零空间 / 行空间 / 列空间 / 左零空间 / 线性无关 / 齐次 / 非齐次
  • 【StarRocks】-- 同步物化视图实战指南
  • 【C++项目】微服务即时通讯系统:服务端
  • 开源WordPress APP(LaraPressAPP)文档:1.开始使用
  • 单调破题:当指数函数遇上线性方程的奇妙对决
  • 【C++】vector 的使用和底层
  • 指标体系单一只关注速度会造成哪些风险
  • 智能体落地与大模型能力关系论
  • QPS、TPS、RT 之间关系
  • Day27_【深度学习(6)—神经网络NN(4)正则化】
  • NeurIPS 2025 spotlight 自动驾驶最新VLA+世界模型 FSDrive
  • Nodejs+html+mysql实现轻量web应用
  • AI模型测评平台工程化实战十二讲(第二讲:目标与指标:把“测评”这件事说清楚(需求到蓝图))