文章目录
一、题目
1. 题目描述
给定两个数组,编写一个函数来计算它们的交集。
说明:
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
- 我们可以不考虑输出结果的顺序。
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
- 如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
2. 示例
示例1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
3. 题目分析
本题没有要求时间复杂度和空间复杂度,可以使用暴力法。(菜的抠脚的我只能想到暴力法o(╥﹏╥)o)
二、解法
1.个人解法
(1)代码
int[] intersect(int[] nums1, int[] nums2) {
// 首先需要对两个数组排序,如果不排序无法解决数组中的重复元素判断。
Arrays.sort(nums1);
Arrays.sort(nums2);
List<Integer> nums3 = new ArrayList<>();
// 用来记录遇到相同元素的下标,防止一个数组中的重复元素与另一个数组中的同一个元素判断是否相同
int flag2 = -1;
//遍历nums1
for (int i = 0; i < nums1.length; i++) {
if (i != 0) {
// 由于数组已经排序,因此相同的元素都在一起,可以通过判断两个相邻元素是否相等来处理相同元素
if (nums1[i] != nums1[i - 1])
// 如果本轮参与比对的元素与上一轮的不同,那么重置flag2
flag2 = -1;
}
//遍历nums2
for (int j = flag2 + 1; j < nums2.length; j++) {
if (nums1[i] == nums2[j]) {
nums3.add(nums1[i]);
// 记录当前找到相同元素的下标,若下一轮进行判断的元素的值与本轮相同,那么找相同元素应该从这个下标之后开始找
flag2 = j;
// 在nums2中找到第一个重复的元素就跳出,因为可能nums2有相同元素。
break;
}
}
}
int[] nums = new int[nums3.size()];
for (int i = 0; i < nums3.size(); i++) {
nums[i] = nums3.get(i);
}
return nums;
}
public static void main(String[] args) {
int[] nums1 = {4, 9, 5};
int[] nums2 = {9, 4, 9, 8, 4};
Arrayjiaoji arr = new Arrayjiaoji();
int[] nums3 = arr.intersect(nums1, nums2);
for (int num : nums3) {
System.out.println(num);
}
}
(2)解法分析
使用暴力法的难点在于遍历的过程中识别重复元素,以及避免重复元素的重复判断。
处理nums1中相同元素的方法:
1.将nums1排序,这样相同的元素都在一起。可以很方便的识别nums1数组参与遍历的相同元素。
2.设立记录变量flag,这个flag用于保存本轮判断nums1中与nums2中相同元素的nums2数组的下标,若下一轮参与遍历的nums1中的元素与本轮参与遍历的元素相同,则从nums2的flag+1下标开始遍历判断。
(3)算法分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
(3)解法缺点
- 时间复杂度太高。
- 构思如何防止重复元素的重复判断耗时太多。
(4)提交截图
2、官网高星解法
(1)排序+双指针
解法分析
个人解法也用到了排序,但是没有把排序后的数组性质利用好,仅仅是为了避免重复元素的重复判断,核心的判断代码还是两层遍历。
排序的目的:
大小有序,因为第一个数组中较大的元素没必要再和第二个数组中较小的元素进行比较,排序后再操作可以节省时间。
双指针:
在排序的基础上,利用双指针可以并行移动两个数组中的元素,遍历两个数组一次,即可得到结果。
代码
/**
* 排序+双指针
*
* @param nums1
* @param nums2
* @return
*/
int[] intersect1(int[] nums1, int[] nums2) {
//将两个数组排序
Arrays.sort(nums1);
Arrays.sort(nums2);
//用于存放相同元素
List<Integer> nums3 = new ArrayList<>();
//初始化两个指针
int flag1 = 0;
int flag2 = 0;
//利用两个指针进行遍历
while (flag1 < nums1.length && flag2 < nums2.length) {
//发现两个相同的元素,则将该元素放入nums3,两个指针同时向前移动一位
if (nums1[flag1] == nums2[flag2]) {
nums3.add(nums1[flag1]);
flag1++;
flag2++;
//如果第一个数组中的元素小于第二个数组中的元素,则第一个数组的指针移动
} else if (nums1[flag1] < nums2[flag2]) {
flag1++;
//如果第二个数组中的元素小于第一个数组中的元素,则第二个数组的指针移动
} else if (nums1[flag1] > nums2[flag2]) {
flag2++;
}
}
int[] nums = new int[nums3.size()];
// 将arraylist类型转换为要求的int数组
for (int i = 0; i < nums3.size(); i++) {
nums[i] = nums3.get(i);
}
return nums;
}
public static void main(String[] args) {
int[] nums1 = {4, 9, 4};
int[] nums2 = {9, 4, 9, 8, 4};
Arrayjiaoji arr = new Arrayjiaoji();
int[] nums3 = arr.intersect1(nums1, nums2);
for (int num : nums3) {
System.out.println(num);
}
}
算法分析
设m和n分别为两个数组的长度,则,对两个数组排序的时间复杂度为:
O
(
m
l
o
g
m
+
n
l
o
g
n
)
O(mlogm+nlogn)
O(mlogm+nlogn),遍历两个数组的时间复杂度为
O
(
m
+
n
)
O(m+n)
O(m+n),因此:
时间复杂度:
O
(
m
l
o
g
m
+
n
l
o
g
n
)
O(mlogm+nlogn)
O(mlogm+nlogn)
空间复杂度:
2
O
(
m
i
n
(
m
,
n
)
)
2O(min(m,n))
2O(min(m,n))
改进
这里的空间复杂度为 2 O ( m i n ( m , n ) ) 2O(min(m,n)) 2O(min(m,n))是因为我写的时候先创建了一个ArrayList来放结果,之后又创建了一个int数组来返回结果,浪费了空间,参考高分解答后,这里可以进行精简:
//两个数组中相同元素的个数不会超过两个元素的最小长度
int[] nums3 = new int[Math.min(nums1.length, nums2.length)];
//用来记录nums3中的元素个数及下标
int flag3 = 0;
....
//利用Arrays.copyOfRange方法来截取我们需要的部分返回
return Arrays.copyOfRange(nums3, 0, flag3);
提交截图
(2)HashMap
解法分析
对第一个数组遍历:
用HashMap的key表示第一个数组数组中的元素,value来存放对应元素在第一个数组中出现的频率。
对第二个数组遍历:
判断是否与map中的key重复,若重复,将值放置结果数组,并且map中key对应的value-1,
代码
int[] intersect3(int[] nums1, int[] nums2) {
// 创建存放结果的数组
int[] nums = new int[Math.min(nums1.length, nums2.length)];
// 记录结果数组中元素个数即最后元素的下标
int flag = 0;
// 实例化一个map
Map<Integer, Integer> map = new HashMap<>();
// 把nums1中的元素和出现次数存放于map
for (int i = 0; i < nums1.length; i++) {
if (map.containsKey(nums1[i])) {
map.put(nums1[i], map.get(nums1[i]) + 1);
} else {
map.put(nums1[i], 1);
}
}
// 遍历nums2,如果与map的key相同,则存于结果数组,并且将key对应的value-1
for (int i = 0; i < nums2.length; i++) {
if (map.containsKey(nums2[i])) {
nums[flag] = nums2[i];
flag++;
map.put(nums2[i], map.get(nums2[i]) - 1);
if (map.get(nums2[i]) == 0) {
map.remove(nums2[i]);
}
}
}
// 返回截取后的结果数组
return Arrays.copyOfRange(nums, 0, flag);
}
public static void main(String[] args) {
int[] nums1 = {4, 9, 4, 8, 9};
int[] nums2 = {9, 4, 9, 8, 4};
Arrayjiaoji arr = new Arrayjiaoji();
int[] nums3 = arr.intersect3(nums1, nums2);
for (int num : nums3) {
System.out.println(num);
}
}
算法分析
设m和n为两个数组的长度,遍历两个数组的时间复杂度为
O
(
m
)
O(m)
O(m)和
O
(
n
)
O(n)
O(n),且操作HashMap时间复杂度为
O
(
1
)
O(1)
O(1),因此:
时间复杂度为:
O
(
m
+
n
)
O(m+n)
O(m+n)
空间复杂度为:
O
(
m
i
n
(
m
,
n
)
)
O(min(m,n))
O(min(m,n))
改进
如果提前将两个数组的大小进行比较,将长度较短的数组放入HashMap,遍历长度较长的数组进行比较,这样可以进一步节约时间。
实现:
在函数的开头添加:
以交换参数的形式,来不占用额外空间的交换数组。
// 如果nums1的长度大于nums2,那么交换两个参数
if (nums1.length > nums2.length)
return intersect(nums2, nums1);