统计可重复列表中的TOP N

news2025/3/22 5:26:16

文章目录

      • 方案1:HashMap统计 + 全排序
        • 实现步骤:
        • 代码实现:
        • 优缺点:
      • 方案2:HashMap统计 + 最小堆(优先队列)
        • 实现步骤:
        • 代码实现:
        • 优缺点:
      • 方案3:Java Stream API
        • 实现步骤:
        • 代码实现:
        • 优缺点:
      • 完整示例代码
      • 关键点总结
      • 方案4:并行流处理(Parallel Stream)
        • 实现步骤:
        • 代码实现:
        • 优缺点:
      • 方案5:桶排序(Bucket Sort)
        • 实现步骤:
        • 代码实现:
        • 优缺点:
      • 方案6:快速选择(Quickselect)算法
        • 实现步骤:
        • 代码实现(部分):
        • 优缺点:
      • 方案7:Guava库的MultiSet(第三方依赖)
        • 实现步骤:
        • 代码实现:
        • 优缺点:
    • 二、方案对比总表
    • 三、总结建议

这种统计top值的情况场景使用的不少,面试过程中也有聊到过这类问题,在这详细介绍一下思路和方案

在Java中统计列表中出现次数最多的前N个对象,常见的实现方案及其优缺点如下:


方案1:HashMap统计 + 全排序

实现步骤:
  1. 使用HashMap统计每个元素的频率。
  2. 将统计结果转为列表,按频率降序排序。
  3. 取前N个元素。
代码实现:
public static List<Map.Entry<String, Integer>> topNWithSort(List<String> list, int n) {
    // 统计频率
    Map<String, Integer> freqMap = new HashMap<>();
    for (String item : list) {
        freqMap.put(item, freqMap.getOrDefault(item, 0) + 1);
    }
    // 转换为列表并排序
    List<Map.Entry<String, Integer>> entries = new ArrayList<>(freqMap.entrySet());
    entries.sort((a, b) -> b.getValue().compareTo(a.getValue()));
    // 取前N个
    return entries.subList(0, Math.min(n, entries.size()));
}
优缺点:
  • 优点:实现简单,代码直观。
  • 缺点:全排序时间复杂度为 (O(m \log m))((m) 为不同元素的数量),当 (m) 较大时效率低。

方案2:HashMap统计 + 最小堆(优先队列)

实现步骤:
  1. 使用HashMap统计频率。
  2. 使用大小为N的最小堆,遍历频率表,维护堆顶为当前最小的频率。
  3. 将堆中元素逆序输出。
代码实现:
public static List<Map.Entry<String, Integer>> topNWithHeap(List<String> list, int n) {
    // 统计频率
    Map<String, Integer> freqMap = new HashMap<>();
    for (String item : list) {
        freqMap.put(item, freqMap.getOrDefault(item, 0) + 1);
    }
    // 初始化最小堆(按频率升序)
    PriorityQueue<Map.Entry<String, Integer>> heap = new PriorityQueue<>(
        (a, b) -> a.getValue() - b.getValue()
    );
    // 遍历频率表,维护堆的大小为N
    for (Map.Entry<String, Integer> entry : freqMap.entrySet()) {
        if (heap.size() < n) {
            heap.offer(entry);
        } else if (entry.getValue() > heap.peek().getValue()) {
            heap.poll();
            heap.offer(entry);
        }
    }
    // 将堆转换为列表并逆序
    List<Map.Entry<String, Integer>> result = new ArrayList<>(heap);
    result.sort((a, b) -> b.getValue().compareTo(a.getValue()));
    return result;
}
优缺点:
  • 优点:时间复杂度为 (O(m \log n)),适合大数据量且 (n \ll m) 的场景。
  • 缺点:需要手动维护堆,代码稍复杂。

方案3:Java Stream API

实现步骤:
  1. 使用StreamgroupingBycounting统计频率。
  2. 按频率降序排序后取前N个。
代码实现:
public static List<Map.Entry<String, Long>> topNWithStream(List<String> list, int n) {
    return list.stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .entrySet().stream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
        .limit(n)
        .collect(Collectors.toList());
}
优缺点:
  • 优点:代码简洁,函数式编程风格。
  • 缺点:隐藏实现细节,可能对内存和性能控制不足。


完整示例代码

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class TopNFrequency {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple");
        int n = 2;

        // 方法1:全排序
        System.out.println("HashMap + Sorting: " + topNWithSort(list, n));
        // 方法2:最小堆
        System.out.println("HashMap + Heap: " + topNWithHeap(list, n));
        // 方法3:Stream API
        System.out.println("Stream API: " + topNWithStream(list, n));
    }

    // 方法1:全排序
    public static List<Map.Entry<String, Integer>> topNWithSort(List<String> list, int n) {
        Map<String, Integer> freqMap = new HashMap<>();
        for (String item : list) {
            freqMap.put(item, freqMap.getOrDefault(item, 0) + 1);
        }
        List<Map.Entry<String, Integer>> entries = new ArrayList<>(freqMap.entrySet());
        entries.sort((a, b) -> b.getValue().compareTo(a.getValue()));
        return entries.subList(0, Math.min(n, entries.size()));
    }

    // 方法2:最小堆
    public static List<Map.Entry<String, Integer>> topNWithHeap(List<String> list, int n) {
        Map<String, Integer> freqMap = new HashMap<>();
        for (String item : list) {
            freqMap.put(item, freqMap.getOrDefault(item, 0) + 1);
        }
        PriorityQueue<Map.Entry<String, Integer>> heap = new PriorityQueue<>(
            (a, b) -> a.getValue() - b.getValue()
        );
        for (Map.Entry<String, Integer> entry : freqMap.entrySet()) {
            if (heap.size() < n) {
                heap.offer(entry);
            } else if (entry.getValue() > heap.peek().getValue()) {
                heap.poll();
                heap.offer(entry);
            }
        }
        List<Map.Entry<String, Integer>> result = new ArrayList<>(heap);
        result.sort((a, b) -> b.getValue().compareTo(a.getValue()));
        return result;
    }

    // 方法3:Stream API
    public static List<Map.Entry<String, Long>> topNWithStream(List<String> list, int n) {
        return list.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
            .entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(n)
            .collect(Collectors.toList());
    }
}

关键点总结

  • 全排序适合数据量小的场景,代码简单但效率低。
  • 最小堆适合大数据量,时间复杂度更优。
  • Stream API以简洁性取胜,但需注意类型转换和性能。

方案4:并行流处理(Parallel Stream)

实现步骤:
  1. 使用并行流加速统计和排序。
  2. 利用ConcurrentHashMap保证线程安全。
代码实现:
public static List<Map.Entry<String, Long>> topNParallelStream(List<String> list, int n) {
    return list.parallelStream()
        .collect(Collectors.groupingByConcurrent(Function.identity(), Collectors.counting()))
        .entrySet().parallelStream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
        .limit(n)
        .collect(Collectors.toList());
}
优缺点:
  • 优点:利用多核并行处理,适合超大数据量。
  • 缺点:线程安全控制复杂,可能因数据倾斜导致性能提升有限。

方案5:桶排序(Bucket Sort)

实现步骤:
  1. 统计频率,记录最大频率。
  2. 创建频率桶,索引为频率,值为元素列表。
  3. 从高到低遍历桶,收集前N个元素。
代码实现:
public static List<Map.Entry<String, Integer>> topNBucketSort(List<String> list, int n) {
    Map<String, Integer> freqMap = new HashMap<>();
    int maxFreq = 0;
    for (String item : list) {
        int freq = freqMap.getOrDefault(item, 0) + 1;
        freqMap.put(item, freq);
        maxFreq = Math.max(maxFreq, freq);
    }
    // 创建桶(索引为频率)
    List<List<String>> buckets = new ArrayList<>(maxFreq + 1);
    for (int i = 0; i <= maxFreq; i++) {
        buckets.add(new ArrayList<>());
    }
    freqMap.forEach((k, v) -> buckets.get(v).add(k));
    // 从高到低收集结果
    List<Map.Entry<String, Integer>> result = new ArrayList<>();
    for (int i = maxFreq; i >= 0 && result.size() < n; i--) {
        for (String item : buckets.get(i)) {
            result.add(new AbstractMap.SimpleEntry<>(item, i));
            if (result.size() == n) break;
        }
    }
    return result;
}
优缺点:
  • 优点:时间复杂度 (O(m + k))((k)为最大频率),适合频率分布集中的场景。
  • 缺点:空间复杂度 (O(k)),若最大频率极高则浪费内存。

方案6:快速选择(Quickselect)算法

实现步骤:
  1. 统计频率,将Entry存入列表。
  2. 使用快速选择算法找到第N大的频率分界点。
  3. 对前N个元素进行排序。
代码实现(部分):
public static List<Map.Entry<String, Integer>> topNQuickSelect(List<String> list, int n) {
    Map<String, Integer> freqMap = new HashMap<>();
    for (String item : list) {
        freqMap.put(item, freqMap.getOrDefault(item, 0) + 1);
    }
    List<Map.Entry<String, Integer>> entries = new ArrayList<>(freqMap.entrySet());
    quickSelect(entries, n);
    return entries.subList(0, n).stream()
        .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
        .collect(Collectors.toList());
}

private static void quickSelect(List<Map.Entry<String, Integer>> list, int n) {
    int left = 0, right = list.size() - 1;
    while (left <= right) {
        int pivotIndex = partition(list, left, right);
        if (pivotIndex == n) break;
        else if (pivotIndex < n) left = pivotIndex + 1;
        else right = pivotIndex - 1;
    }
}

private static int partition(List<Map.Entry<String, Integer>> list, int low, int high) {
    int pivotValue = list.get(high).getValue();
    int i = low;
    for (int j = low; j < high; j++) {
        if (list.get(j).getValue() > pivotValue) {
            Collections.swap(list, i, j);
            i++;
        }
    }
    Collections.swap(list, i, high);
    return i;
}
优缺点:
  • 优点:平均时间复杂度 (O(m)),适合对性能要求极高的场景。
  • 缺点:实现复杂,需处理大量边界条件。

方案7:Guava库的MultiSet(第三方依赖)

实现步骤:
  1. 使用Guava的Multiset统计频率。
  2. 按频率排序后取前N个。
代码实现:
public static List<Multiset.Entry<String>> topNGuava(List<String> list, int n) {
    Multiset<String> multiset = HashMultiset.create(list);
    return multiset.entrySet().stream()
        .sorted((a, b) -> b.getCount() - a.getCount())
        .limit(n)
        .collect(Collectors.toList());
}
优缺点:
  • 优点:代码极简,依赖Guava工具类。
  • 缺点:需引入第三方库,不适合纯JDK环境。

二、方案对比总表

方案时间复杂度空间复杂度适用场景
全排序(O(m \log m))(O(m))数据量小,代码简单
最小堆(O(m \log n))(O(n))大数据量且 (n \ll m)
Stream API(O(m \log m))(O(m))快速开发,代码简洁
并行流(O(m \log m / p))(O(m))多核环境,超大数据量
桶排序(O(m + k))(O(k))频率集中且最大值已知
快速选择(O(m))(平均)(O(m))高性能需求,允许复杂实现
Guava MultiSet(O(m \log m))(O(m))允许第三方依赖

三、总结建议

  1. 小数据量:优先使用 Stream API全排序,代码简洁。
  2. 大数据量:选择 最小堆并行流,平衡性能与内存。
  3. 已知频率分布:尝试 桶排序 优化时间和空间。
  4. 极高性能需求:考虑 快速选择(需自行处理实现复杂度)。
  5. 允许第三方库Guava 可大幅简化代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PowerBI纯小白如何驾驭DAX公式一键生成:copilot for fabric

在2025年2月份更新中&#xff0c;powerbi desktop里的copilot功能还新增了一个非常强大的功能&#xff1a;一键生成多个度量值&#xff0c;并直接加载到模型。 直接上示例展示&#xff1a; 打开DAX查询视图&#xff0c;在copilot窗格中直接输入想要生成多个度量值&#xff0c…

两市总的净流出和净流入来分析情况

为了排查数据干扰&#xff0c;只从两市总的净流出和净流入来分析情况。 净流出才对应资金抽离&#xff1a;若净流入为负&#xff08;即净流出&#xff09;&#xff0c;则意味着资金从股市中撤出&#xff0c;例如主动卖出的金额超过主动买入金额。净流入反映市场信心&#xff1…

如何在SQL中高效使用聚合函数、日期函数和字符串函数:实用技巧与案例解析

文章目录 聚合函数group by子句的使用实战OJ日期函数字符串函数数学函数其它函数 聚合函数 函数说明COUNT([DISTINCT] expr)返回查询到的数据的 数量SUM([DISTINCT] expr)返回查询到的数据的 总和&#xff0c;不是数字没有意义AVG([DISTINCT] expr)返回查询到的数据的 平均值&…

k8s-coredns-CrashLoopBackOff 工作不正常

本文作者&#xff1a; slience_me 问题描述 # 问题描述 # rootk8s-node1:/home/slienceme# kubectl get pods --all-namespaces # NAMESPACE NAME READY STATUS RESTARTS AGE # kube-flannel kube-flannel-ds-66bcs …

【Android性能】Systrace分析

1&#xff0c;分析工具 1&#xff0c;Systrace新UI网站 Perfetto UI 2&#xff0c;Systrace抓取 可通过android sdk中自带的systrace抓取&#xff0c;路径一般如下&#xff0c;..\AppData\Local\Android\Sdk\platform-tools&#xff0c; 另外需要安装python2.7&#xff0c;…

Unity导出WebGL,无法显示中文

问题&#xff1a;中文无法显示 默认字体无法显示中文 在编辑器中设置了中文和英文的按钮&#xff0c;中文按钮无法显示 导出后无法显示中文 解决办法&#xff1a; 自己添加字体&#xff0c;导入项目&#xff0c;并引用 示例 下载一个字体文件&#xff0c;这里使用的阿里…

oracle事务的组成

1)数据库事务由以下的部分组成: 一个或多个DML 语句 ; 一个 DDL(Data Definition Language – 数据定义语言) 语句&#xff1b; 一个 DCL(Data Control Language – 数据控制语言)语句&#xff1b; 2)事务的执行开始&#xff1a; 以第一个 DML 语句的执行作为开始 &#xff0c;…

【如何在OpenWebUI中使用FLUX绘画:基于硅基流动免费API的完整指南】

如何在OpenWebUI中使用FLUX绘画&#xff1a;基于硅基流动免费API的完整指南 注册并获取硅基流动秘钥OpenWebUI中使用函数配置自定义模型-提示词配置效果验证 ) FLUX绘画是一种强大的AI绘图工具&#xff0c;本文将详细介绍如何在OpenWebUI中集成并使用FLUX绘画功能&#xff0c;…

Facebook 如何影响元宇宙的发展趋势

Facebook 如何影响元宇宙的发展趋势 引言 元宇宙&#xff08;Metaverse&#xff09;这个概念&#xff0c;曾经只存在于科幻小说中&#xff0c;如今正逐渐成为现实。它是一个由多个 3D 虚拟世界组成的网络&#xff0c;用户可以在其中进行社交、游戏、工作等活动。Facebook&…

1.5.7 掌握Scala内建控制结构 - 变量作用域

本次实战深入理解了Scala中变量作用域的概念&#xff0c;通过两个任务演示了作用域的基本规则。在任务1中&#xff0c;我们创建了一个名为ScopeDemo01的对象&#xff0c;展示了内部作用域能够访问外部作用域的变量。通过在if语句块中访问在外部定义的message变量&#xff0c;我…

RAID磁盘阵列管理

一. 什么是RAID RAID是英文Redundant Array of Independent Disks的缩写&#xff0c;中文翻译过来就是“独立冗余磁盘阵列”。简单的说&#xff0c;RAID是一种把多块独立的硬盘&#xff08;物理硬盘&#xff09;按不同的方式组合起来形成一个硬盘组&#xff08;逻辑硬盘&#…

微博ip属地不发微博会不会变

随着社交媒体的普及&#xff0c;微博作为其中的佼佼者&#xff0c;一直备受关注。而且微博上线了显示用户IP属地的功能&#xff0c;这一功能旨在减少冒充热点事件当事人、恶意造谣、蹭流量等不良行为&#xff0c;确保传播内容的真实性和透明度。然而&#xff0c;这也引发了一些…

C#从入门到精通(1)

目录 第一章 C#与VS介绍 第二章 第一个C#程序 &#xff08;1&#xff09;C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 &#xff08;2&#xff09;程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…

仿“东方甄选”直播商城小程序运营平台

在公域直播流量红利趋于饱和、流量成本大幅攀升的当下&#xff0c;私域直播为企业开辟了新的流量聚集和转化渠道&#xff0c;特别是对于那些希望在私域流量领域取得突破的品牌商家来说&#xff0c;直播场景以其独特的高频互动氛围&#xff0c;相比其他运营方式&#xff0c;展现…

ORACLE 19.8版本数据库环境EXPDP导数据的报错处理

近期用户在做EXPDP导出时&#xff0c;报错异常termination终止;EXPDP本身是简单的功能并且这个环境也是经常做导出的&#xff0c;到底是什么原因导致了这个问题呢&#xff1f; 导出脚本报错&#xff1a; 分析导出日志&#xff0c;当时系统资源充足但是进程启动失败&#xff0c;…

LabVIEW运动控制(二):EtherCAT运动控制器的多轴示教加工应用(下)

前面两节课程分别给大家介绍了“控制器连接、定时获取轴状态、轴坐标、控制器型号、轴参数设置、IO控制、Basic文件下载”&#xff08;详情点击→LabVIEW运动控制&#xff08;二&#xff09;&#xff1a;EtherCAT运动控制器的多轴示教加工应用&#xff08;上&#xff09;&#…

纯内网环境安装1Panel面板与商店应用

文章目录 前序准备开始联网机器配置1Panel配置安装所有离线需要的应用导出Docker镜像导出1Panel配置 离线机器配置安装1Panel覆盖配置导入容器镜像重建应用 关于Jar包的运行 前序 之前一篇文章讲解了如何在内网环境下安装1Panel并操作商店应用安装应用程序&#xff0c;但是在一…

OpenCV计算摄影学(22)将输入的彩色图像转换为两种风格的铅笔素描效果函数pencilSketch()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 铅笔风格非写实线描图。 该函数通过图像处理技术将输入的彩色图像转换为两种风格的铅笔素描效果&#xff1a; dst1&#xff1a;炭笔效果的灰度图…

影响单模光纤耦合效率的分析

影响单模光纤耦合效率的因素 如果想使单模光纤的耦合效率最高&#xff0c;入射光束需要满足以下条件&#xff1a; &#xff08;1&#xff09;入射光束接近高斯光&#xff1b; &#xff08;2&#xff09;入射光束从光纤端面正入射&#xff1b; &#xff08;3&#xff09;入射…

windows+ragflow+deepseek实战之一excel表查询

ragflows平台部署参考文章 Win10系统Docker+DeepSeek+ragflow搭建本地知识库 ragflow通过python实现参考这篇文章 ragflow通过python实现 文章目录 背景效果1、准备数据2、创建知识库3、上传数据并解析4、新建聊天助理5、测试会话背景 前面已经基于Win10系统Docker+DeepSeek+…