Java 实例 – 数组交集:从入门到实战
在日常开发中,我们经常会遇到需要找出两个数组中共同元素的场景。比如,用户 A 和用户 B 的好友列表中,有哪些是共同的好友?又比如,两个商品库存列表中,哪些商品是重叠的?这类问题本质上就是“数组交集”问题。
Java 实例 – 数组交集 是一个非常典型的算法应用场景,它不仅考验你对数组操作的理解,也涉及集合、循环、去重等多个核心知识点。今天,我们就来一步步拆解这个问题,用真实代码带你掌握它的多种实现方式。
什么是数组交集?
在数学中,两个集合的交集指的是同时属于这两个集合的元素。在 Java 中,数组虽然不是集合(Collection),但我们可以把它们当作“有序的元素列表”来处理。
举个例子:
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = {3, 4, 5, 6, 7};
这两个数组的交集就是 {3, 4, 5},因为只有这三个数字同时出现在两个数组中。
📌 小贴士:交集的元素顺序不重要,但通常我们按原数组顺序或排序后输出。
创建数组与初始化
在开始计算交集前,我们需要先创建并初始化两个数组。Java 中数组是固定长度的容器,一旦创建就不能改变长度,但可以修改其中的元素。
// 创建两个整型数组
int[] array1 = {1, 2, 3, 4, 5};
int[] array2 = {3, 4, 5, 6, 7};
// 也可以使用 new 关键字手动创建
int[] array3 = new int[5];
array3[0] = 10;
array3[1] = 20;
array3[2] = 30;
array3[3] = 40;
array3[4] = 50;
✅ 注意:数组索引从 0 开始,
array3[4]是最后一个元素。
在实际项目中,我们可能从用户输入、文件读取或数据库查询中获取数据,然后转为数组。但今天为了聚焦核心逻辑,我们直接使用静态数据进行演示。
方法一:暴力遍历法(双重循环)
这是最直观、最容易理解的方法。它的思路是:遍历第一个数组的每一个元素,再遍历第二个数组,检查是否相等。
public static int[] findIntersection(int[] arr1, int[] arr2) {
// 用于存储结果的临时数组,初始长度设为较小数组的长度
int[] result = new int[Math.min(arr1.length, arr2.length)];
int index = 0; // 记录结果数组当前填充位置
// 外层循环:遍历第一个数组
for (int i = 0; i < arr1.length; i++) {
// 内层循环:遍历第二个数组
for (int j = 0; j < arr2.length; j++) {
// 如果两个元素相等,说明是交集元素
if (arr1[i] == arr2[j]) {
// 避免重复添加(重要!)
boolean isDuplicate = false;
for (int k = 0; k < index; k++) {
if (result[k] == arr1[i]) {
isDuplicate = true;
break;
}
}
// 如果不是重复元素,就加入结果
if (!isDuplicate) {
result[index++] = arr1[i];
}
}
}
}
// 将结果数组裁剪到实际长度,避免多余 0 值
int[] finalResult = new int[index];
for (int i = 0; i < index; i++) {
finalResult[i] = result[i];
}
return finalResult;
}
🔍 关键点说明:
- 使用
index来跟踪结果数组的写入位置。Math.min(arr1.length, arr2.length)是一个合理的预估上限,但最终长度可能更小。- 去重逻辑非常关键,否则会出现
[3, 4, 5, 3, 4, 5]这样的错误结果。- 时间复杂度为 O(n × m × k),其中 n、m 是数组长度,k 是去重检查的平均次数。
这个方法虽然效率不高,但逻辑清晰,特别适合初学者理解“交集”是怎么一步步找出来的。
方法二:使用 HashSet 提升效率
当数组较大时,暴力法会变得非常慢。这时我们可以借助 HashSet 的快速查找特性来优化。
HashSet 是基于哈希表实现的集合,它可以在 O(1) 时间内判断某个元素是否存在。
import java.util.HashSet;
import java.util.Set;
public static int[] findIntersectionOptimized(int[] arr1, int[] arr2) {
// 将第二个数组的元素放入 HashSet,便于快速查找
Set<Integer> set = new HashSet<>();
for (int num : arr2) {
set.add(num);
}
// 用于存储结果的集合(自动去重)
Set<Integer> resultSet = new HashSet<>();
// 遍历第一个数组,检查每个元素是否在 set 中
for (int num : arr1) {
if (set.contains(num)) {
resultSet.add(num); // 自动去重
}
}
// 将 Set 转回数组
int[] result = new int[resultSet.size()];
int index = 0;
for (int num : resultSet) {
result[index++] = num;
}
return result;
}
✅ 优势分析:
- 时间复杂度优化为 O(n + m),远快于暴力法。
- 代码更简洁,去重由
HashSet自动处理。- 适用于数据量较大的场景,比如处理用户标签、商品 ID 等。
💡 比喻:把
arr2的元素放进一个“快速查找柜子”(HashSet),然后逐个检查arr1的元素是否在这个柜子里。如果在,就记下来。
方法三:排序 + 双指针法(适用于已排序数组)
如果两个数组本身是有序的,我们可以使用“双指针”技巧,进一步优化空间和时间。
import java.util.Arrays;
public static int[] findIntersectionSorted(int[] arr1, int[] arr2) {
// 先排序(如果原数组未排序)
Arrays.sort(arr1);
Arrays.sort(arr2);
int i = 0, j = 0; // 双指针
int[] result = new int[Math.min(arr1.length, arr2.length)];
int index = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] == arr2[j]) {
// 避免重复添加
if (index == 0 || result[index - 1] != arr1[i]) {
result[index++] = arr1[i];
}
i++;
j++;
} else if (arr1[i] < arr2[j]) {
i++; // arr1 当前元素太小,移动 i
} else {
j++; // arr2 当前元素太小,移动 j
}
}
// 裁剪数组
return Arrays.copyOf(result, index);
}
📌 运行逻辑:
- 两个指针分别指向两个数组的开头。
- 如果元素相等,加入结果并同时移动两个指针。
- 如果
arr1[i] < arr2[j],说明arr1的值太小,只能移动i。- 反之移动
j。
✅ 优势:
- 空间复杂度为 O(1)(不考虑结果数组)。
- 无需额外集合,适合内存受限环境。
- 适合已排序数据,如数据库查询结果。
实际案例:好友共同关注列表
假设我们有两个用户的好友列表:
int[] user1Friends = {101, 203, 305, 407, 509};
int[] user2Friends = {305, 407, 601, 702, 803};
我们想找出他们共同关注的人:
int[] commonFriends = findIntersectionOptimized(user1Friends, user2Friends);
System.out.println("共同好友 ID:" + Arrays.toString(commonFriends));
// 输出:共同好友 ID:[305, 407]
这个逻辑可以直接用于社交平台、推荐系统、权限匹配等场景。
性能对比表
| 方法 | 时间复杂度 | 空间复杂度 | 是否去重 | 适用场景 |
|---|---|---|---|---|
| 暴力遍历 | O(n×m×k) | O(n) | 手动实现 | 小数组、教学演示 |
| HashSet 法 | O(n + m) | O(m) | 自动实现 | 通用、推荐 |
| 双指针法 | O(n log n + m log m) | O(1) | 手动控制 | 已排序数据、内存敏感 |
✅ 推荐:除非数据已排序或内存极受限,否则优先使用 HashSet 法。
总结与建议
Java 实例 – 数组交集 并不是一个孤立的问题,它背后蕴含着算法设计的通用思想:如何在效率与可读性之间找到平衡。
- 初学者建议从暴力法入手,理解“交集”本质;
- 中级开发者应掌握 HashSet 优化方案,提升代码性能;
- 高级场景可考虑双指针或并行计算(如
parallelStream)。
最后提醒一点:不要盲目追求最优解,先写对,再写快。尤其是在面试或项目初期,清晰的代码比“理论上更快”的代码更有价值。
如果你在项目中遇到类似需求,不妨先用 HashSet 实现,再根据性能表现决定是否优化。
记住,每一个算法的背后,都是对问题本质的拆解与重构。多动手、多思考,你的 Java 技能一定会稳稳提升。