【经典算法】LeetCode350:两个数组的交集 II(Java/C/Python3/JavaScript实现含注释说明,Easy)

简介: 【经典算法】LeetCode350:两个数组的交集 II(Java/C/Python3/JavaScript实现含注释说明,Easy)

题目描述

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,
应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

原题:LeetCode 14

思路及实现

方式一:哈希表

思路

由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。

首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。

为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。

代码实现

Java版本
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Map<Integer, Integer> numMap = new HashMap<>();
        List<Integer> res = new ArrayList<>();
        
        // 遍历 nums1 数组,将元素及其出现次数存储在哈希表中
        for (int num : nums1) {
            numMap.put(num, numMap.getOrDefault(num, 0) + 1);
        }
        // 遍历 nums2 数组,检查每个元素是否在哈希表中出现
        // 如果出现,将该元素添加到结果集中,并将哈希表中的对应出现次数减1
        for (int num : nums2) {
            if (numMap.containsKey(num) && numMap.get(num) > 0) {
                res.add(num);
                numMap.put(num, numMap.get(num) - 1);
            }
        }
        
        // 将结果集转换为数组输出
        int[] result = new int[res.size()];
        for (int i = 0; i < res.size(); i++) {
            result[i] = res.get(i);
        }
        
        return result;
    }

说明:

使用哈希表来求解两个数组的交集,并将结果集转换为数组输出。

首先创建一个哈希表 numMap 来存储第一个数组 nums1 中每个元素及其出现的次数。 创建一个列表 res 来存储交集结果。 遍历

nums2 数组,对于每个元素 num,检查其是否在哈希表 numMap 中出现且出现次数大于 0。 如果满足条件,则将该元素加入结果集res 中,并将哈希表中对应出现次数减 1。 将结果集 res 转换为数组输出。 返回最终的结果数组。

tips优化空间:

哈希表 numMap 只用来存储 nums1 中的元素及其出现次数,而不是存储两个数组的交集。可以减少额外空间的使用。

结果集 res 使用列表存储交集结果,并在最后将其转换为数组输出。 优化空间后的复杂度分析与之前相同,时间复杂度为 O(m +

n),空间复杂度为 O(min(m, n))。其中 m 和 n 分别表示两个输入数组的长度。

C语言版本
#include <stdio.h>
#include <stdlib.h>
/**
 * 返回两个整数中的较小值
 */
int min(int a, int b) {
    return a < b ? a : b;
}
int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize) {
    // 使用哈希表来存储nums1中每个元素及其出现的次数
    int* numMap = (int*) malloc(sizeof(int) * 1001);
    for (int i = 0; i < nums1Size; i++) {
        numMap[nums1[i]]++;
    }
    int* res = (int*) malloc(sizeof(int) * min(nums1Size, nums2Size));  // 结果集使用动态分配的数组来存储
    int resSize = 0;  // 结果集的大小
    // 遍历nums2数组,检查每个元素在哈希表中是否存在
    // 如果存在且对应出现次数大于0,则加入结果集,并将对应出现次数减1
    for (int i = 0; i < nums2Size; i++) {
        if (numMap[nums2[i]] > 0) {
            res[resSize++] = nums2[i];
            numMap[nums2[i]]--;
        }
    }
    *returnSize = resSize;  // 将结果集的大小赋给返回值
    free(numMap);  // 释放动态分配的内存
    return res;  // 返回结果集数组的指针
}

说明:

使用数组 numMap 来存储 nums1 数组中每个元素及其出现的次数,数组下标表示元素值。 遍历 nums2 数组,对于每个元素nums2[i],如果 numMap[nums2[i]] 大于 0,则将 nums2[i] 添加到结果集 res 中,并将

numMap[nums2[i]] 减 1。 使用动态分配的数组 res 来存储交集结果,动态分配的内存大小为较小的数组大小

min(nums1Size, nums2Size)。 将结果集的大小 resSize 赋给

returnSize,即将结果集的大小返回给调用函数。 使用 free() 函数释放动态分配的内存。

Python3版本
class Solution:
    def intersect(self, nums1, nums2):
        # 使用哈希表来存储nums1中每个元素及其出现的次数
        numMap = {}
        for num in nums1:
            numMap[num] = numMap.get(num, 0) + 1
        res = []
        # 遍历nums2数组,检查每个元素在哈希表中是否存在
        # 如果存在且对应出现次数大于0,则加入结果集,并将对应出现次数减1
        for num in nums2:
            if num in numMap and numMap[num] > 0:
                res.append(num)
                numMap[num] -= 1
        return res

说明:

使用字典 numMap 来存储 nums1 数组中每个元素及其出现的次数。 遍历 nums2 数组,对于每个元素 num,如果num 在 numMap 中存在且对应出现次数大于 0,则将 num 添加到结果集 res 中,并将 numMap[num] 减 1。返回结果集 res。

JavaScript版本
function intersect(nums1, nums2) {
  const numMap = new Map();
  const intersection = [];
  
  // Step 1: 对 nums1 数组进行哈希计数,存储元素及其出现次数
  for (const num of nums1) {
    numMap.set(num, numMap.get(num) + 1 || 1);
  }
  
  // Step 2: 遍历 nums2 数组,检查每个元素是否在哈希表中出现
  // 如果出现,则将该元素添加到结果数组中,并将哈希表中的对应出现次数减1
  for (const num of nums2) {
    if (numMap.has(num) && numMap.get(num) > 0) {
      intersection.push(num);
      numMap.set(num, numMap.get(num) - 1);
    }
  }
  
  return intersection;
}
// 测试用例
/*
const nums1 = [1, 2, 2, 1];
const nums2 = [2, 2];
const result = intersect(nums1, nums2);
console.log(result);
*/

代码说明:

1. 使用 Map() 对象创建了一个空的哈希表 numMap,它将用于存储元素及其出现次数。

2. 在第一个 for-of 循环中,遍历 nums1 数组,并通过 numMap.set() 方法将元素及其出现次数存储在哈希表中。使用numMap.get(num) + 1 || 1

语法,在哈希表中获取元素对应的出现次数,如果不存在则默认设置为 1。

3. 在第二个 for-of 循环中,遍历 nums2 数组,并通过 numMap.has(num) 检查元素是否在哈希表中出现,以及numMap.get(num) 获取对应出现次数。

4. 如果元素在哈希表中出现且出现次数大于 0,则将该元素添加到结果数组 intersection 中,并通过 numMap.set(num,numMap.get(num) - 1) 将哈希表中的对应出现次数减 1。

5. 最后,将结果数组 intersection 返回作为函数的结果。

6. 在测试用例中,创建了示例数组 nums1 和 nums2,并调用 intersect 函数获取交集结果。最后,将结果打印到控制台。

复杂度分析

  • 时间复杂度:O(max(m, n)),其中 m 和 n 分别表示两个输入数组的长度。需要遍历两个数组并对哈希表进行操作,哈希表操作的时间复杂度是 O(1),因此总时间复杂度与两个数组的长度和呈线性关系。
  • 空间复杂度:O(min(m, n)),表示两个输入数组中较小的那个数组的长度

方式二:排序 + 双指针

思路

如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。

初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。

代码实现

Java版本
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        // 对两个数组进行排序
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        
        // 获取两个数组的长度
        int length1 = nums1.length, length2 = nums2.length;
        
        // 创建结果数组,长度为两个数组中较小的长度
        int[] intersection = new int[Math.min(length1, length2)];
        
        // 初始化指针和结果数组的索引
        int index1 = 0, index2 = 0, index = 0;
        
        // 双指针法求交集
        while (index1 < length1 && index2 < length2) {
            if (nums1[index1] < nums2[index2]) {
                index1++;  // nums1的元素较小,移动nums1的指针
            } else if (nums1[index1] > nums2[index2]) {
                index2++;  // nums2的元素较小,移动nums2的指针
            } else {
                // 相等,加入结果数组,同时移动两个指针和结果数组的索引
                intersection[index] = nums1[index1];
                index1++;
                index2++;
                index++;
            }
        }
        
        // 返回交集结果数组,利用Arrays.copyOfRange()截取结果数组的有效部分
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

说明:

首先,对两个输入的数组 nums1 和 nums2 进行排序,这里使用了 Arrays.sort() 方法。时间复杂度为

O(nlogn),其中 n 分别表示两个数组的长度。 初始化指针 index1 和 index2 分别指向数组 nums1 和 nums2

的起始位置,同时初始化结果数组的索引 index 为 0。 创建结果数组 intersection,长度为两个数组长度较小的那个。

使用双指针法进行比较: 如果 nums1[index1] 小于 nums2[index2],说明 nums1 的元素较小,将 index1

向后移动。 如果 nums1[index1] 大于 nums2[index2],说明 nums2 的元素较小,将 index2 向后移动。

如果 nums1[index1] 等于 nums2[index2],说明找到了一个交集元素,将该元素加入结果数组 intersection

中,并将两个指针和结果数组的索引都向后移动。 当有一个指针越界时,表示已经遍历完其中一个数组,此时得到的结果数组即为两个数组的交集。

最后,利用 Arrays.copyOfRange() 方法截取结果数组 intersection 的有效部分(0 到

index-1),并返回新的数组作为输出。

C语言版本
#include <stdlib.h>
int cmp(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}
int *intersect(int *nums1, int nums1Size, int *nums2, int nums2Size, int *returnSize) {
    // 对两个数组进行排序
    qsort(nums1, nums1Size, sizeof(int), cmp);
    qsort(nums2, nums2Size, sizeof(int), cmp);
    // 创建结果数组,长度为两个数组中较小的大小
    int *intersection = (int *)malloc(sizeof(int) * (nums1Size < nums2Size ? nums1Size : nums2Size));
    // 初始化指针和结果数组索引
    int index1 = 0, index2 = 0, index = 0;
    // 双指针法求交集
    while (index1 < nums1Size && index2 < nums2Size) {
        if (nums1[index1] < nums2[index2]) {
            index1++;  // nums1的元素较小,移动nums1的指针
        } else if (nums1[index1] > nums2[index2]) {
            index2++;  // nums2的元素较小,移动nums2的指针
        } else {
            // 相等,加入结果数组,同时移动两个指针和结果数组的索引
            intersection[index] = nums1[index1];
            index1++;
            index2++;
            index++;
        }
    }
    // 返回交集结果数组的大小
    *returnSize = index;
    return intersection;
}

说明:

使用qsort()函数对输入的两个数组nums1和nums2进行排序。这里使用了自定义的比较函数cmp()来指定排序规则。排序后,两个数组中的元素将按照从小到大的顺序排列。

创建一个结果数组intersection,长度为两个数组中较小的那个。使用动态内存分配函数malloc()来分配存储交集结果所需的内存空间。

初始化两个指针index1和index2,分别指向数组nums1和nums2的起始位置。同时,初始化结果数组的索引index为0。

使用双指针法进行比较遍历: 如果nums1[index1]小于nums2[index2],说明nums1的元素较小,将index1向后移动。

如果nums1[index1]大于nums2[index2],说明nums2的元素较小,将index2向后移动。

如果nums1[index1]等于nums2[index2],说明找到了一个交集元素,将该元素加入结果数组intersection中,并将两个指针和结果数组的索引都向后移动。

当有一个指针越界时,表示已经遍历完其中一个数组,此时得到的结果数组即为两个数组的交集。

使用指针returnSize来记录交集结果数组的大小。 返回交集结果数组intersection的指针。

Python3版本
class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 对两个数组进行排序
        nums1.sort()
        nums2.sort()
        # 获取两个数组的长度
        length1, length2 = len(nums1), len(nums2)
        # 创建一个空列表来存储交集结果
        intersection = list()
        # 初始化两个指针
        index1 = index2 = 0
        # 双指针法求交集
        while index1 < length1 and index2 < length2:
            if nums1[index1] < nums2[index2]:
                index1 += 1
            elif nums1[index1] > nums2[index2]:
                index2 += 1
            else:
                # 相等,加入结果列表,同时移动两个指针
                intersection.append(nums1[index1])
                index1 += 1
                index2 += 1
        
        # 返回交集结果列表
        return intersection

说明:

对输入的两个数组nums1和nums2进行排序,这里使用了Python内置的sort()方法,能在原地排序。

创建一个空列表intersection来存储交集结果。

使用双指针法进行比较,分别初始化index1和index2为0,它们分别指向数组nums1和nums2的起始位置。

遍历两个数组,比较当前指针位置上的元素大小。 如果nums1[index1]小于nums2[index2],则index1向右移动。

如果nums1[index1]大于nums2[index2],则index2向右移动。

如果nums1[index1]等于nums2[index2],则找到一个交集元素,加入结果列表intersection中,并同时移动两个指针。

当有一个指针越界时,表示已经遍历完其中一个数组,那么结果列表intersection中存储的就是两个数组的交集。

返回交集结果列表intersection。

JavaScript版本
function intersect(nums1, nums2) {
  // 对两个数组进行排序
  nums1.sort((a, b) => a - b);
  nums2.sort((a, b) => a - b);
  const intersection = [];
  let i = 0; // nums1 数组的指针
  let j = 0; // nums2 数组的指针
  // 双指针法求交集
  while (i < nums1.length && j < nums2.length) {
    if (nums1[i] < nums2[j]) {
      i++; // nums1 数组当前元素较小,指针向右移动
    } else if (nums1[i] > nums2[j]) {
      j++; // nums2 数组当前元素较小,指针向右移动
    } else {
      // 相等,加入结果数组,同时移动两个指针
      intersection.push(nums1[i]);
      i++;
      j++;
    }
  }
  return intersection;
}
/*
// 测试用例
const nums1 = [1, 2, 2, 1];
const nums2 = [2, 2];
const result = intersect(nums1, nums2);
console.log(result);
*/

说明:

对 nums1 和 nums2 数组进行排序,确保相同的元素相邻。

创建空数组 intersection 存储交集结果,以及指针 i = 0 和 j = 0。

使用双指针法进行求交集:

a. 如果 nums1[i] === nums2[j],则将该元素加入结果数组 intersection 中,
并将两个指针向右移动;
b. 如果 nums1[i] < nums2[j],则将指针 i 向右移动;
c. 如果 nums1[i] > nums2[j],则将指针 j 向右移动。

当任一数组遍历完毕时,算法结束。

返回结果数组 intersection,即为交集结果。

在测试用例中,创建示例数组 nums1 和 nums2,并调用 intersect 函数获取交集结果。最后,将结果打印到控制台。

复杂度分析

  • 时间复杂度:O(mlogm+nlogn),其中 m 和 n 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 O(mlogm+nlogn),遍历两个数组的时间复杂度是 O(m+n),因此总时间复杂度是 O(mlogm+nlogn)。
  • 空间复杂度:O(min(m,n)),其中 m 和 n 分别是两个数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。(不过在 C语言班长中,我们可以直接创建一个 vector,不需要把答案临时存放在一个额外的数组中,所以这种实现的空间复杂度为 O(1))。

总结

哈希表法 排序和双指针法
时间复杂度 O(n + m) (n 和 m 分别为两个数组的长度) O(nlogn + mlogm)(n 和 m 分别为两个数组的长度)
空间复杂度 O(min(n, m)) (n 和 m 分别为两个数组的长度) O(min(m,n)) (C语言版本为O(1),不包括存储结果的数组,则为)
提前排序 不需要 需要
额外空间 需要 不需要
适用场景 未排序的数组 已排序的数组
优点 - 不需要提前排序数组;- 适用于大多数情况下的数组交集问题 - 不需要额外的空间用于存储哈希表;- 不需要提前排序数组
缺点 - 需要额外的空间用于存储哈希表 - 需要对数组进行排序,增加了时间复杂度 ; - 不适用于非有序数组和需要保持有序的结果

相似题目

相似题目 难度 链接
两个数组的交集 简单 leetcode-349
求两个数组的交集 简单 leetcode-面试题16.11
最小区间 困难 leetcode-632
寻找重复数 中等 leetcode-287
相关文章
|
5天前
|
机器学习/深度学习 数据采集 搜索推荐
Python基于深度学习算法实现图书推荐系统项目实战
Python基于深度学习算法实现图书推荐系统项目实战
|
5天前
|
机器学习/深度学习 数据采集 算法
Python实现ISSA融合反向学习与Levy飞行策略的改进麻雀优化算法优化支持向量机回归模型(SVR算法)项目实战
Python实现ISSA融合反向学习与Levy飞行策略的改进麻雀优化算法优化支持向量机回归模型(SVR算法)项目实战
|
3天前
|
搜索推荐 C++ Python
Python排序算法大PK:归并VS快速,谁才是你的效率之选?
【7月更文挑战第13天】归并排序** 使用分治法,稳定且平均时间复杂度O(n log n),适合保持元素顺序和并行处理。
12 5
|
4天前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
【7月更文挑战第12天】归并排序是高效稳定的排序算法,采用分治策略。Python 实现包括递归地分割数组及合并已排序部分。示例代码展示了如何将 `[12, 11, 13, 5, 6]` 分割并归并成有序数组 `[5, 6, 11, 12, 13]`。虽然 $O(n log n)$ 时间复杂度优秀,但需额外空间,适合大规模数据排序。对于小规模数据,可考虑其他算法。**
18 4
|
5天前
|
机器学习/深度学习 数据采集 算法
Python实现WOA智能鲸鱼优化算法优化支持向量机分类模型(SVC算法)项目实战
Python实现WOA智能鲸鱼优化算法优化支持向量机分类模型(SVC算法)项目实战
|
3天前
|
存储 算法 Python
“解锁Python高级数据结构新姿势:图的表示与遍历,让你的算法思维跃升新高度
【7月更文挑战第13天】Python中的图数据结构用于表示复杂关系,通过节点和边连接。常见的表示方法是邻接矩阵(适合稠密图)和邻接表(适合稀疏图)。图遍历包括DFS(深度优先搜索)和BFS(广度优先搜索):DFS深入探索分支,BFS逐层访问邻居。掌握这些技巧对优化算法和解决实际问题至关重要。**
10 1
|
5天前
|
机器学习/深度学习 数据采集 算法
Python实现ISSA融合反向学习与Levy飞行策略的改进麻雀优化算法优化支持向量机分类模型(SVC算法)项目实战
Python实现ISSA融合反向学习与Levy飞行策略的改进麻雀优化算法优化支持向量机分类模型(SVC算法)项目实战
|
5天前
|
机器学习/深度学习 数据采集 算法
Python实现GBDT(梯度提升树)分类模型(GradientBoostingClassifier算法)并应用网格搜索算法寻找最优参数项目实战
Python实现GBDT(梯度提升树)分类模型(GradientBoostingClassifier算法)并应用网格搜索算法寻找最优参数项目实战
|
5天前
|
机器学习/深度学习 数据采集 算法
Python实现PCA降维和KNN人脸识别模型(PCA和KNeighborsClassifier算法)项目实战
Python实现PCA降维和KNN人脸识别模型(PCA和KNeighborsClassifier算法)项目实战
|
5天前
|
算法 搜索推荐 编译器
算法高手养成记:Python快速排序的深度优化与实战案例分析
【7月更文挑战第11天】快速排序是编程基础,以O(n log n)时间复杂度和原址排序著称。其核心是“分而治之”,通过选择基准元素分割数组并递归排序两部分。优化包括:选择中位数作基准、尾递归优化、小数组用简单排序。以下是一个考虑优化的Python实现片段,展示了随机基准选择。通过实践和优化,能提升算法技能。**
10 3