统计可重复列表中的TOP N
文章目录
- 方案1:HashMap统计 + 全排序
- 实现步骤:
- 代码实现:
- 优缺点:
- 方案2:HashMap统计 + 最小堆(优先队列)
- 实现步骤:
- 代码实现:
- 优缺点:
- 方案3:Java Stream API
- 实现步骤:
- 代码实现:
- 优缺点:
- 完整示例代码
- 关键点总结
- 方案4:并行流处理(Parallel Stream)
- 实现步骤:
- 代码实现:
- 优缺点:
- 方案5:桶排序(Bucket Sort)
- 实现步骤:
- 代码实现:
- 优缺点:
- 方案6:快速选择(Quickselect)算法
- 实现步骤:
- 代码实现(部分):
- 优缺点:
- 方案7:Guava库的MultiSet(第三方依赖)
- 实现步骤:
- 代码实现:
- 优缺点:
- 二、方案对比总表
- 三、总结建议
这种统计top值的情况场景使用的不少,面试过程中也有聊到过这类问题,在这详细介绍一下思路和方案
在Java中统计列表中出现次数最多的前N个对象,常见的实现方案及其优缺点如下:
方案1:HashMap统计 + 全排序
实现步骤:
- 使用
HashMap统计每个元素的频率。 - 将统计结果转为列表,按频率降序排序。
- 取前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统计 + 最小堆(优先队列)
实现步骤:
- 使用
HashMap统计频率。 - 使用大小为N的最小堆,遍历频率表,维护堆顶为当前最小的频率。
- 将堆中元素逆序输出。
代码实现:
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());// 遍历频率表,维护堆的大小为Nfor (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
实现步骤:
- 使用
Stream的groupingBy和counting统计频率。 - 按频率降序排序后取前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 APISystem.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 APIpublic 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)
实现步骤:
- 使用并行流加速统计和排序。
- 利用
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)
实现步骤:
- 统计频率,记录最大频率。
- 创建频率桶,索引为频率,值为元素列表。
- 从高到低遍历桶,收集前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)算法
实现步骤:
- 统计频率,将
Entry存入列表。 - 使用快速选择算法找到第N大的频率分界点。
- 对前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(第三方依赖)
实现步骤:
- 使用Guava的
Multiset统计频率。 - 按频率排序后取前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)) | 允许第三方依赖 |
三、总结建议
- 小数据量:优先使用 Stream API 或 全排序,代码简洁。
- 大数据量:选择 最小堆 或 并行流,平衡性能与内存。
- 已知频率分布:尝试 桶排序 优化时间和空间。
- 极高性能需求:考虑 快速选择(需自行处理实现复杂度)。
- 允许第三方库:Guava 可大幅简化代码。
相关文章:
统计可重复列表中的TOP N
文章目录 方案1:HashMap统计 全排序实现步骤:代码实现:优缺点: 方案2:HashMap统计 最小堆(优先队列)实现步骤:代码实现:优缺点: 方案3:Java Str…...
PowerBI纯小白如何驾驭DAX公式一键生成:copilot for fabric
在2025年2月份更新中,powerbi desktop里的copilot功能还新增了一个非常强大的功能:一键生成多个度量值,并直接加载到模型。 直接上示例展示: 打开DAX查询视图,在copilot窗格中直接输入想要生成多个度量值,…...
Pytest的夹具
1、pytest的前置后置夹具 fixture 有些内容是在每个用例执行之前都要运行操作:-- 用例前置 接口:购物车模块先登录 --登录结果 【token鉴权】 UI: 每次用例 打开浏览器 --driver 有些内容在每个用例之后都要运行操作:–用例后置 接口: 数据清除 UI:关闭浏览器 叫做用例的…...
两市总的净流出和净流入来分析情况
为了排查数据干扰,只从两市总的净流出和净流入来分析情况。 净流出才对应资金抽离:若净流入为负(即净流出),则意味着资金从股市中撤出,例如主动卖出的金额超过主动买入金额。净流入反映市场信心࿱…...
GitHub在push推送到远程仓库的时候显示Logon failed登录失败
具体问题描述 git.exe push --progress "origin" master:master Logon failed, use ctrlc to cancel basic credential prompt. remote: Support for password authentication was removed on August 13, 2021. 这是因为Git 推送失败的原因是 GitHub 已经不支持密码认…...
如何在SQL中高效使用聚合函数、日期函数和字符串函数:实用技巧与案例解析
文章目录 聚合函数group by子句的使用实战OJ日期函数字符串函数数学函数其它函数 聚合函数 函数说明COUNT([DISTINCT] expr)返回查询到的数据的 数量SUM([DISTINCT] expr)返回查询到的数据的 总和,不是数字没有意义AVG([DISTINCT] expr)返回查询到的数据的 平均值&…...
AutoGen :使用 Swarm 构建自治型多智能体团队
👉👉👉本人承接各类AI相关应用开发项目(包括但不限于大模型微调、RAG、AI智能体、NLP、机器学习算法、运筹优化算法、数据分析EDA等) !!!👉👉👉 有意愿请私信!!!AutoGen 的 AgentChat 模块提供了一种强大的方法来构建多智能体协作系统。 在之前的文章中,我们探讨了…...
RK3568平台设备树文件功能解析(鸿蒙系统篇)
鸿蒙设备树驱动修改时候发现目录下有很多的rk3568 的设备树,由于对这些设备树功能不太熟悉,所以索性就整理一下不同设备树的功能 rk3568-evb1-ddr4-v10.dts rk3568-evb4-lp3-v10.dts rk3568-evb6-ddr3-v10-rk628-rgb2hdmi.dts …...
k8s-coredns-CrashLoopBackOff 工作不正常
本文作者: 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,分析工具 1,Systrace新UI网站 Perfetto UI 2,Systrace抓取 可通过android sdk中自带的systrace抓取,路径一般如下,..\AppData\Local\Android\Sdk\platform-tools, 另外需要安装python2.7,…...
Unity导出WebGL,无法显示中文
问题:中文无法显示 默认字体无法显示中文 在编辑器中设置了中文和英文的按钮,中文按钮无法显示 导出后无法显示中文 解决办法: 自己添加字体,导入项目,并引用 示例 下载一个字体文件,这里使用的阿里…...
oracle事务的组成
1)数据库事务由以下的部分组成: 一个或多个DML 语句 ; 一个 DDL(Data Definition Language – 数据定义语言) 语句; 一个 DCL(Data Control Language – 数据控制语言)语句; 2)事务的执行开始: 以第一个 DML 语句的执行作为开始 ,…...
【如何在OpenWebUI中使用FLUX绘画:基于硅基流动免费API的完整指南】
如何在OpenWebUI中使用FLUX绘画:基于硅基流动免费API的完整指南 注册并获取硅基流动秘钥OpenWebUI中使用函数配置自定义模型-提示词配置效果验证 ) FLUX绘画是一种强大的AI绘图工具,本文将详细介绍如何在OpenWebUI中集成并使用FLUX绘画功能,…...
QT 磁盘文件 教程04-创建目录、删除目录、遍历目录
【1】新建目录 bool CreateDir(QString name){QString fileName name ;QDir dir(fileName);if (dir.isEmpty()) {dir.mkdir(fileName);return true;}else{qDebug()<<"文件夹已存在";return false;} } 【2】删除目录 bool DeleteDir(QString fileName){if (…...
Event driven agentic document workflows 笔记 - 2
代理文档工作流(ADW)- 课程笔记 Agentic Document Workflows (ADW) 1. 课程目标 介绍 代理文档工作流(ADW) 背后的核心概念,包括: RAG(检索增强生成)代理工作流 探讨如何利用 事件…...
Facebook 如何影响元宇宙的发展趋势
Facebook 如何影响元宇宙的发展趋势 引言 元宇宙(Metaverse)这个概念,曾经只存在于科幻小说中,如今正逐渐成为现实。它是一个由多个 3D 虚拟世界组成的网络,用户可以在其中进行社交、游戏、工作等活动。Facebook&…...
1.5.7 掌握Scala内建控制结构 - 变量作用域
本次实战深入理解了Scala中变量作用域的概念,通过两个任务演示了作用域的基本规则。在任务1中,我们创建了一个名为ScopeDemo01的对象,展示了内部作用域能够访问外部作用域的变量。通过在if语句块中访问在外部定义的message变量,我…...
RAID磁盘阵列管理
一. 什么是RAID RAID是英文Redundant Array of Independent Disks的缩写,中文翻译过来就是“独立冗余磁盘阵列”。简单的说,RAID是一种把多块独立的硬盘(物理硬盘)按不同的方式组合起来形成一个硬盘组(逻辑硬盘&#…...
利用ffmpeg库实现音频AAC编解码
AAC(Advanced Audio Coding)是一种音频编码技术,出现于1997年,基于MPEG-2的音频编码技术。AAC具有高效的数据压缩能力和较高的音质,适用于各种音频应用场景。例如,在智能设备中,AAC技术被广泛…...
微博ip属地不发微博会不会变
随着社交媒体的普及,微博作为其中的佼佼者,一直备受关注。而且微博上线了显示用户IP属地的功能,这一功能旨在减少冒充热点事件当事人、恶意造谣、蹭流量等不良行为,确保传播内容的真实性和透明度。然而,这也引发了一些…...
appium之Toast元素识别
Appium之Toast元素识别教程与实例 一、Toast简介 Toast是Android系统中的轻量级消息提示框,以浮动形式短暂显示(通常2-3秒),无法被点击且不会获取焦点。常见于登录失败、操作提示等场景,如“密码错误”或“网络异常”。…...
「JavaScript深入」WebSocket:高效的双向实时通信技术
WebSocket WebSocket 的特点1. 全双工通信2. 持久连接3. 低延迟4. 二进制和文本支持5. 连接管理6. 二进制数据传输 WebSocket 协议详解1. 握手过程2. 数据帧结构 WebSocket 的实现服务器端实现(Node.js ws库)1. 基础服务器2. 广播功能实现3. 心跳机制客…...
C#从入门到精通(1)
目录 第一章 C#与VS介绍 第二章 第一个C#程序 (1)C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 (2)程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…...
配置阿里云yum源
配置阿里云yum源 修改默认的yum仓库,把原有的移动到创建的目录里(踢出国外的yum源) # 切换到/ect/yum.repos.d/目录下 cd /etc/yum.repos.d/ # 新建repo目录 mkdir repo # 把原有的移动到创建的目录里 mv ./*.repo ./repo/配置yum源 # 找到…...
头歌实训--Pandas合并数据集--第3关:案例:美国各州的统计数据
任务描述 本关为练习关卡,请按照编程要求完成任务,获取美国各州2010年的人口密度排名。 import pandas as pd import numpy as npdef task3():#********** Begin **********##读取三个csv文件pop pd.DataFrame(pd.read_csv("./step3/state-popula…...
仿“东方甄选”直播商城小程序运营平台
在公域直播流量红利趋于饱和、流量成本大幅攀升的当下,私域直播为企业开辟了新的流量聚集和转化渠道,特别是对于那些希望在私域流量领域取得突破的品牌商家来说,直播场景以其独特的高频互动氛围,相比其他运营方式,展现…...
CentOS 7.9 安装 Python 3.10 详细步骤及常见问题解决
一、环境准备与依赖安装 更新系统与开发工具 sudo yum update -y sudo yum groupinstall "Development Tools" -y sudo yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel \ readline-devel tk-devel libffi-devel gdbm-devel db4-de…...
ORACLE 19.8版本数据库环境EXPDP导数据的报错处理
近期用户在做EXPDP导出时,报错异常termination终止;EXPDP本身是简单的功能并且这个环境也是经常做导出的,到底是什么原因导致了这个问题呢? 导出脚本报错: 分析导出日志,当时系统资源充足但是进程启动失败,…...
LabVIEW运动控制(二):EtherCAT运动控制器的多轴示教加工应用(下)
前面两节课程分别给大家介绍了“控制器连接、定时获取轴状态、轴坐标、控制器型号、轴参数设置、IO控制、Basic文件下载”(详情点击→LabVIEW运动控制(二):EtherCAT运动控制器的多轴示教加工应用(上)&#…...
Ubuntu Qt: no service found for - “org.qt-project.qt.mediaplayer“
1、前言 在一次项目过程中,因项目需求,需要将windows开发的Qt项目迁移到ubuntu系统中,且在某个功能项中需要播放音频,在windows系统中能够正常运行,但在ubuntu系统中却显示defaultServiceProvider::requestService(): …...
