统计可重复列表中的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统计 + 全排序

实现步骤:
  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());// 遍历频率表,维护堆的大小为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

实现步骤:
  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 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)

实现步骤:
  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.rhkb.cn/news/37727.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型的微调技术(高效微调原理篇)

背景 公司有需求做农业方向的大模型应用以及Agent助手&#xff0c;那么适配农业数据就非常重要。但众所周知&#xff0c;大模型的全量微调对算力资源要求巨大&#xff0c;在现实的限制条件下基本“玩不起”&#xff0c;那么高效微调技术就非常必要。为了更好地对微调技术选型和…

Java 大视界 -- Java 大数据在智能家居设备联动与场景自动化中的应用(140)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

面试八股 —— Redis篇

重点&#xff1a;缓存 和 分布式锁 缓存&#xff08;穿透&#xff0c;击穿&#xff0c;雪崩&#xff09; 降级可作为系统的保底策略&#xff0c;适用于穿透&#xff0c;击穿&#xff0c;雪崩 1.缓存穿透 2.缓存击穿 3.缓存雪崩 缓存——双写一致性 1.强一致性业务&#xff08…

[网络安全] 滥用Azure内置Contributor角色横向移动至Azure VM

本文来源于团队的超辉老师&#xff0c;其系统分析了Azure RBAC角色模型及其在权限滥用场景下的攻击路径。通过利用AADInternals工具提升用户至Contributor角色&#xff0c;攻击者可在Azure VM中远程执行命令&#xff0c;创建后门账户&#xff0c;实现横向移动。文中详述了攻击步…

OO_Unit1

第一次作业 UML类图 代码复杂度分析 其中Expr中的toString方法认知复杂度比较高&#xff0c;主要源于多层条件嵌套和分散的字符串处理逻辑&#xff0c;重构时可重点关注这两部分的解耦。 代码量分析 1.”通用形式“ 我觉得我的设计的最大特点就是“通用形式”&#xff0c;具…

阿里云 AI 搜索产品荣获 Elastic Innovation Award 2024

阿里云AI搜索产品荣获Elastic Innovation Award 2024&#xff0c;该奖项于近日在新加坡ElasticON 2025的Elastic合作伙伴峰会上颁发&#xff0c;旨在表彰基于Elastic平台开发企业级生成式人工智能&#xff08;GenAI&#xff09;应用的顶尖合作伙伴&#xff0c;这些应用有效帮助…

网络原理之网络层、数据链路层

1. 网络层 1.1 IP协议 1.1.1 基本概念 主机: 配有IP地址,但是不进⾏路由控制的设备路由器: 即配有IP地址,⼜能进⾏路由控制节点: 主机和路由器的统称 1.1.2 协议头格式 说明&#xff1a; 4位版本号(version): 指定IP协议的版本,对于IPv4来说,就是4,对于IPv6来说,就是6 4位头…

炫酷的3D按钮效果实现 - CSS3高级特性应用

炫酷的3D按钮效果实现 - CSS3高级特性应用 这里写目录标题 炫酷的3D按钮效果实现 - CSS3高级特性应用项目介绍核心技术实现1. 基础结构设计2. 视觉效果实现2.1 背景渐变2.2 立体感营造 3. 交互动效设计3.1 悬停效果3.2 按压效果 技术要点分析1. 深度层次感2. 动画过渡3. 性能优…

Java定时任务的三重境界:从单机心跳到分布式协调

《Java定时任务的三重境界&#xff1a;从单机心跳到分布式协调》 本文将以生产级代码标准&#xff0c;揭秘Java定时任务从基础API到分布式调度的6种实现范式&#xff0c;深入剖析ScheduledThreadPoolExecutor与Quartz Scheduler的线程模型差异&#xff0c;并给出各方案的性能压…

鸿蒙Flutter开发故事:不,你不需要鸿蒙化

在华为牵头下&#xff0c;Flutter 鸿蒙化如火如荼进行&#xff0c;当第一次看到一份上百个插件的Excel 列表时&#xff0c;我也感到震惊&#xff0c;排名前 100 的插件赫然在列&#xff0c;这无疑是一次大规模的军团作战。 然后&#xff0c;参战团队鱼龙混杂&#xff0c;难免有…

PolyBench基准程序详解:编译器优化评测指标

PolyBench基准程序详解&#xff1a;编译器优化评测指标 PolyBench基本概念 PolyBench&#xff08;Polyhedral Benchmark&#xff09;是由UCLA&#xff08;加州大学洛杉矶分校&#xff09;的Louis-Nol Pouchet及其研究团队开发的基准测试套件&#xff0c;专门用于评估多面体编…

2025年渗透测试面试题总结-某四字大厂实习面试复盘 一面 二面 三面(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 一面 1. 数组和链表各自的优势和原因 2. 操作系统层面解析和进程 3. 线程和进程通信方式及数据安全问…

ruoyi-vue部署4

1.jdk-linux安装 2.tomcat-linux安装 3.ruoy后台部署 4.nginx-linux安装5.ruoyi前端部署​​​​​​​

查看visual studio的MSVC版本的方法

右键项目名称&#xff0c;下拉点击属性 然后点击库目录&#xff0c;下拉点击编辑 就可以看见msvc版本了

【Javascrip】Javascript练习01 REST API using Express.js.

针对该问题的项目路径 要求部分 what you need to doReview the tasks provided in the section below.Obtain the boilerplate code.Use your local development environment to implement a solution.Upload your solution for marking via Gradescope. There is no attempt…

【蓝桥杯速成】| 9.回溯升级

题目一&#xff1a;组合综合 问题描述 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返…

apache-maven-3.9.9 详细安装配置教程(2025版)

apache-maven-3.9.9 详细安装配置教程 一、下载解压二、配置本地仓库镜像源三、配置环境变量四、配置 IDEA 一、下载解压 官网地址&#xff1a; https://maven.apache.org/download.cgi二、配置本地仓库镜像源 解压并新建文件夹&#xff0c;作为 maven 下载仓库。目的&#…

构建企业级数据的愿景、目标与规划历程

文章目录 1. 企业级数据的愿景2. 企业级数据的目标、实施标准和战略3. 企业级数据的蓝图3.1 业务数字化转型的蓝图3.2 大数据平台的架构蓝图 4. 企业级数据的规划历程4.1 第一阶段&#xff1a;数据生产与打通4.2 第二阶段&#xff1a;数据集成、联接、应用 伴随着数字科技、通信…

深入理解 JavaScript/TypeScript 中的假值(Falsy Values)与逻辑判断 ✨

&#x1f579;️ 深入理解 JavaScript/TypeScript 中的假值&#xff08;Falsy Values&#xff09;与逻辑判断 在 JavaScript/TypeScript 开发中&#xff0c;if (!value) 是最常见的条件判断之一。它看似简单&#xff0c;却隐藏着语言的核心设计逻辑&#xff0c;也是许多开发者…

74HC04(反相器)和74HC14(反相器、施密特触发器)的区别

74HC04和74HC14的具体区别详解 同样具有反相器功能&#xff0c;你知道74HC04和74HC14的具体区别吗&#xff1f; 74HC04 对于74HC04很好理解&#xff0c;输入低电平&#xff0c;输出高电平&#xff1b;输入高电平&#xff0c;输出低电平。 建议操作条件&#xff1a; 下图是TI的…