Java 实例 – 查找数组中的重复元素
在日常开发中,我们经常会遇到需要从一个数据集合中找出重复项的场景。比如用户注册时检测用户名是否重复、学生成绩统计中排查重复分数、或者处理日志文件时发现重复的错误记录。这些需求背后的核心逻辑,往往都离不开对“重复元素”的识别与处理。
而数组作为 Java 中最基础、最常用的数据结构之一,自然也成为了这类问题的试验场。今天我们就来深入探讨一个经典且实用的 Java 实例:查找数组中的重复元素。无论你是刚接触编程的初学者,还是有一定经验的中级开发者,这篇文章都会帮你从零开始,掌握多种高效、可靠的实现方式。
什么是数组中的重复元素?
简单来说,重复元素就是指在数组中出现了超过一次的值。例如,数组 [1, 3, 2, 3, 4, 1] 中,数字 1 和 3 都出现了两次,因此它们就是重复元素。
这个看似简单的任务,其实蕴含了多种解法思路,每种方式在时间复杂度、空间复杂度和可读性上都有所不同。掌握这些方法,不仅能解决当前问题,还能锻炼你的算法思维。
创建数组与初始化
在开始查找之前,我们先来搭建一个测试用的数据环境。这里我们定义一个整型数组,并手动初始化一些包含重复元素的数据。
// 定义一个整数数组,用于测试查找重复元素
int[] numbers = { 1, 3, 2, 3, 4, 1, 5, 6, 2 };
// 输出原始数组内容,便于观察
System.out.println("原始数组: " + Arrays.toString(numbers));
注释:
Arrays.toString()是 Java 提供的工具方法,用于将数组转换为可读的字符串格式。它位于java.util.Arrays包中,使用前需导入。
方法一:使用嵌套循环暴力查找
这是最直观、最容易理解的方法。通过两层循环,让每个元素都与其余所有元素比较,一旦发现相等且位置不同,就认为是重复元素。
// 方法一:嵌套循环暴力查找
System.out.println("\n--- 方法一:嵌套循环暴力查找 ---");
// 用于记录已发现的重复元素(避免重复输出)
Set<Integer> foundDuplicates = new HashSet<>();
// 外层循环:遍历每个元素
for (int i = 0; i < numbers.length; i++) {
// 内层循环:与当前元素之后的所有元素比较
for (int j = i + 1; j < numbers.length; j++) {
// 如果发现相等,说明是重复元素
if (numbers[i] == numbers[j]) {
foundDuplicates.add(numbers[i]); // 将重复值加入集合
break; // 找到一个重复即可跳出内层循环,避免重复记录
}
}
}
// 输出所有找到的重复元素
if (foundDuplicates.isEmpty()) {
System.out.println("未发现重复元素");
} else {
System.out.println("找到的重复元素: " + foundDuplicates);
}
注释:
- 外层循环从
i = 0开始,表示当前要检查的元素。- 内层循环从
j = i + 1开始,避免与自身比较,也防止重复检查(如(i,j)和(j,i))。Set<Integer>用于去重,确保同一个数字不会被多次打印。break用于一旦找到重复,立即跳出内层循环,提升效率。
这个方法的时间复杂度是 O(n²),适合小规模数据,但不适合处理大数据量。
方法二:使用 HashMap 统计频次
如果我们想更高效地查找重复元素,可以借助 HashMap 来统计每个元素出现的次数。这是一种典型的“哈希计数”思想。
// 方法二:使用 HashMap 统计频次
System.out.println("\n--- 方法二:使用 HashMap 统计频次 ---");
// 创建一个 HashMap,键为数组元素,值为出现次数
Map<Integer, Integer> countMap = new HashMap<>();
// 遍历数组,统计每个元素出现的次数
for (int num : numbers) {
// 如果该元素已在 map 中,次数加 1;否则初始化为 1
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
// 打印每个元素及其出现次数
System.out.println("各元素出现次数:");
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
System.out.println("数字 " + entry.getKey() + " 出现了 " + entry.getValue() + " 次");
}
// 找出出现次数大于 1 的元素,即重复元素
Set<Integer> duplicates = new HashSet<>();
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
if (entry.getValue() > 1) {
duplicates.add(entry.getKey());
}
}
// 输出结果
if (duplicates.isEmpty()) {
System.out.println("没有重复元素");
} else {
System.out.println("重复元素: " + duplicates);
}
注释:
getOrDefault(key, defaultValue)是 HashMap 的便捷方法,当键不存在时返回默认值。entrySet()返回键值对集合,便于遍历。- 使用
Set<Integer>去重,保证输出结果清晰。
此方法的时间复杂度为 O(n),空间复杂度也为 O(n),在大多数实际场景中性能更优。
方法三:使用 Java 8 Stream API(函数式编程)
Java 8 引入了 Stream API,让集合操作更加简洁优雅。我们也可以用它来实现查找重复元素的功能。
// 方法三:使用 Java 8 Stream API
System.out.println("\n--- 方法三:使用 Java 8 Stream API ---");
// 使用 Stream 流式处理,统计频次并筛选出重复元素
Set<Integer> duplicateStream = Arrays.stream(numbers)
.boxed() // 将 int 转为 Integer,以便使用 Stream 方法
.collect(Collectors.groupingBy(
// 分组依据:元素本身
Integer::intValue,
// 收集器:统计每个元素出现次数
Collectors.counting()
))
.entrySet()
.stream()
.filter(entry -> entry.getValue() > 1) // 只保留出现次数大于 1 的
.map(Map.Entry::getKey) // 提取键(即重复元素)
.collect(Collectors.toSet()); // 收集为 Set
// 输出结果
if (duplicateStream.isEmpty()) {
System.out.println("未找到重复元素");
} else {
System.out.println("通过 Stream 找到的重复元素: " + duplicateStream);
}
注释:
boxed()将基本类型int转为包装类Integer,因为 Stream 操作需要对象类型。groupingBy()是分组收集器,按元素值分组并统计数量。filter和map是流式操作的核心,链式调用清晰表达逻辑。- 最终用
Collectors.toSet()收集为 Set,自动去重。
这个方法代码简洁、可读性强,适合追求代码美感的开发者。
比较与选择建议
为了帮助你更好地选择合适的方法,我们来做一个对比总结:
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 嵌套循环 | O(n²) | O(1) | 无需额外空间,逻辑简单 | 效率低,不适合大数据 |
| HashMap 统计 | O(n) | O(n) | 效率高,逻辑清晰 | 需要额外空间 |
| Stream API | O(n) | O(n) | 代码简洁,函数式风格 | 学习成本稍高,性能略低 |
注释:在实际项目中,如果数据量在几千以内,嵌套循环也能接受;但超过 1 万条数据时,建议优先使用 HashMap 或 Stream。
实际应用场景举例
让我们看一个真实场景:学生成绩表中检查是否有重复分数。
// 模拟学生成绩数据
double[] scores = { 88.5, 92.0, 76.5, 88.5, 90.0, 76.5, 95.0 };
// 使用 HashMap 方法查找重复分数
Map<Double, Integer> scoreCount = new HashMap<>();
for (double score : scores) {
scoreCount.put(score, scoreCount.getOrDefault(score, 0) + 1);
}
Set<Double> duplicateScores = new HashSet<>();
for (Map.Entry<Double, Integer> entry : scoreCount.entrySet()) {
if (entry.getValue() > 1) {
duplicateScores.add(entry.getKey());
}
}
System.out.println("\n--- 成绩表重复分数检测 ---");
if (duplicateScores.isEmpty()) {
System.out.println("成绩无重复");
} else {
System.out.println("存在重复分数: " + duplicateScores);
}
这个例子说明,Java 实例 – 查找数组中的重复元素 并不只是理论练习,它在真实业务中有着广泛的应用价值。
总结与建议
通过本文的学习,我们系统地掌握了三种查找数组中重复元素的主流方法:暴力循环、HashMap 统计和 Stream API。每种方法各有优劣,适合不同的使用场景。
- 如果你是初学者,建议先从嵌套循环入手,理解问题本质;
- 如果你追求效率,推荐使用 HashMap;
- 如果你喜欢代码优雅,Stream API 是不错的选择。
记住,编程不是追求“最短代码”,而是找到“最合适”的解法。在实际开发中,性能、可读性、维护性三者需要权衡。
最后,希望你能在今后的项目中,灵活运用这些技巧,把“Java 实例 – 查找数组中的重复元素”变成你工具箱里的得力助手。