【经典算法】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
相关文章
|
2月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
41 0
|
4月前
|
算法
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
4月前
|
存储 Java API
LeetCode------合并两个有序数组(4)【数组】
这篇文章介绍了LeetCode上的"合并两个有序数组"问题,并提供了三种解法:第一种是使用Java的Arrays.sort()方法直接对合并后的数组进行排序;第二种是使用辅助数组和双指针技术进行合并;第三种则是从后向前的双指针方法,避免了使用额外的辅助数组。
LeetCode------合并两个有序数组(4)【数组】
LeetCode------找到所有数组中消失的数字(6)【数组】
这篇文章介绍了LeetCode上的"找到所有数组中消失的数字"问题,提供了一种解法,通过两次遍历来找出所有未在数组中出现的数字:第一次遍历将数组中的每个数字对应位置的值增加数组长度,第二次遍历找出所有未被增加的数字,即缺失的数字。
|
4月前
|
前端开发
LeetCode------移动零(5)【数组】
这篇文章介绍了LeetCode上的"移动零"问题,提出了一种使用双指针的原地操作解法,该方法首先将非零元素移动到数组前端并保持相对顺序,然后填充后续位置为零,以达到题目要求。
|
2月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
21 4
|
2月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
22 0
Leetcode第三十三题(搜索旋转排序数组)
|
2月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
69 0
|
2月前
|
C++
【LeetCode 12】349.两个数组的交集
【LeetCode 12】349.两个数组的交集
18 0
|
2月前
|
算法 Java
LeetCode(一)Java
LeetCode(一)Java