Java 实例 – 查找数组中的重复元素(深入浅出)

Java 实例 – 查找数组中的重复元素

在日常开发中,我们经常会遇到需要从一个数据集合中找出重复项的场景。比如用户注册时检测用户名是否重复、学生成绩统计中排查重复分数、或者处理日志文件时发现重复的错误记录。这些需求背后的核心逻辑,往往都离不开对“重复元素”的识别与处理。

而数组作为 Java 中最基础、最常用的数据结构之一,自然也成为了这类问题的试验场。今天我们就来深入探讨一个经典且实用的 Java 实例:查找数组中的重复元素。无论你是刚接触编程的初学者,还是有一定经验的中级开发者,这篇文章都会帮你从零开始,掌握多种高效、可靠的实现方式。


什么是数组中的重复元素?

简单来说,重复元素就是指在数组中出现了超过一次的值。例如,数组 [1, 3, 2, 3, 4, 1] 中,数字 13 都出现了两次,因此它们就是重复元素。

这个看似简单的任务,其实蕴含了多种解法思路,每种方式在时间复杂度、空间复杂度和可读性上都有所不同。掌握这些方法,不仅能解决当前问题,还能锻炼你的算法思维。


创建数组与初始化

在开始查找之前,我们先来搭建一个测试用的数据环境。这里我们定义一个整型数组,并手动初始化一些包含重复元素的数据。

// 定义一个整数数组,用于测试查找重复元素
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() 是分组收集器,按元素值分组并统计数量。
  • filtermap 是流式操作的核心,链式调用清晰表达逻辑。
  • 最终用 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 实例 – 查找数组中的重复元素”变成你工具箱里的得力助手。