【经典算法】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天前
|
存储 Java
Java基础之数组
Java基础之数组
10 2
|
5天前
|
Java 索引
[笔记] 疯狂JAVA讲义(第3版)第4章 流程控制与数组
[笔记] 疯狂JAVA讲义(第3版)第4章 流程控制与数组
|
12天前
|
Java
(JAVA)找出数组中不重复或者重复的数字
(JAVA)找出数组中不重复或者重复的数字
|
存储 Python
【Python入门篇】——Python基础语法(字面量注释与变量)
【Python入门篇】——Python基础语法(字面量注释与变量)
106 0
|
Python
Python入门系列第二章--第二节:注释
Python入门系列第二章--第二节:注释
100 0
|
1天前
|
开发者 Python
【干货】Python编程惯例
【干货】Python编程惯例
6 1
|
4天前
|
Shell Python
GitHub星标破千Star!Python游戏编程的初学者指南
Python 是一种高级程序设计语言,因其简洁、易读及可扩展性日渐成为程序设计领域备受推崇的语言。 目前的编程书籍大多分为两种类型。第一种,与其说是教编程的书,倒不如说是在教“游戏制作软件”,或教授使用一种呆板的语言,使得编程“简单”到不再是编程。而第二种,它们就像是教数学课一样教编程:所有的原理和概念都以小的应用程序的方式呈现给读者。
|
4天前
|
机器学习/深度学习 存储 自然语言处理
惊艳!老司机熬夜总结的Python高性能编程,高效、稳定、快速!
Python 语言是一种脚本语言,其应用领域非常广泛,包括数据分析、自然语言处理机器学习、科学计算、推荐系统构建等。 能够轻松实现和代码跑得够快之间的取舍却是一个世人皆知且令人惋惜的现象而这个问题其实是可以解决的。 有些人想要让顺序执行的过程跑得更快。有些人需要利用多核架构、集群,或者图形处理单元的优势来解决他们的问题。有些人需要可伸缩系统在保证可靠性的前提下酌情或根据资金多少处理更多或更少的工作。有些人意识到他们的编程技巧,通常是来自其他语言,可能不如别人的自然。
|
4天前
|
测试技术 虚拟化 云计算
GitHub高赞!速通Python编程基础手册,被玩出花了!
随着云时代的来临,Python 语言越来越被程序开发人员喜欢和使用,因为其不仅简单易学,而且还有丰富的第三方程序库和相应完善的管理工具。 从命令行脚本程序到 GUI程序,从图形技术到科学计算,从软件开发到自动化测试,从云计算到虚拟化,所有这些领域都有 Python 的身影。 今天给小伙伴们分享的这份手册采用以任务为导向的编写模式,全面地介绍了 Python 编程基础及其相关知识的应用,讲解了如何利用 Python 的知识解决部分实际问题。
GitHub高赞!速通Python编程基础手册,被玩出花了!
|
5天前
|
存储 Python 索引
【Python编程挑战】:单链表实现技巧与最佳实践
【Python编程挑战】:单链表实现技巧与最佳实践

热门文章

最新文章