zoukankan      html  css  js  c++  java
  • 两个数组的交集 II [ LeetCode

     
    原题地址:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/description/
     
    给定两个数组,写一个方法来计算它们的交集。

    例如:

    给定 nums1 = [1, 2, 2, 1], nums2 = [2, 2], 返回 [2, 2].

    注意:

    •    输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
    •    我们可以不考虑输出结果的顺序。

    跟进:

    • 如果给定的数组已经排好序呢?你将如何优化你的算法?
    • 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
    • 如果nums2的元素存储在磁盘上,内存是有限的,你不能一次加载所有的元素到内存中,你该怎么办?

    以上是原题

     
    我们先按照常规思路解题,再逐步分析最后的集中特殊情况。
     
      思路:
      1、增加一个计数器,用来记录其中一个数组元素出现的次数。
      2、遍历另一个数组,如果该数组元素在计数器中有记录且记录的次数大于1,将该数字新增到结果数组中,同时计数器该数字记录的次数减1。
      实现代码如下:
     1     public int[] intersect(int[] nums1, int[] nums2) {
     2         Map<Integer, Integer> counter = new HashMap<>(); //计数器,key为数组中的数字,value为该数字在数组中出现的次数
     3         for (int i = 0; i < nums1.length; i++) {
     4             int num = nums1[i];
     5             if (counter.containsKey(num)) {
     6                 counter.put(num, counter.get(num) + 1);
     7             } else {
     8                 counter.put(num, 1);
     9             }
    10         }
    11         List<Integer> tempList = new ArrayList<>();
    12         for (int i = 0; i < nums2.length; i++) {
    13             int num = nums2[i];
    14             if (counter.containsKey(num) && counter.get(num) > 0) {
    15                 counter.put(num, counter.get(num) - 1); //计数器中记录该数字的次数减1
    16                 tempList.add(num); //将该数字添加到list中
    17             }
    18         }
    19         int[] result = new int[tempList.size()];
    20         //为满足题目返回值类型,将list转换为int数组
    21         for (int i = 0; i < result.length; i++) {
    22             result[i] = tempList.get(i);
    23         }
    24         return result;
    25     }

      OK,基本功能已经实现,下一步我们一起思考如何满足几个跟进问题:

    • 如果给定的数组已经排好序呢?你将如何优化你的算法?

      思路:因为两个数组都是有序的,那我们完全可以用两个指针c1和c2分别顺序扫描两个数组,得到两个数字m和n,有以下三种关系:

      1、m == n,则该数字是重复数字,将该数字添加到结果数组中,同时将两个指针分别后移一位。

      2、m > n,我们需要将c2指针后移一位。

      3、m < n,我们需要将c1指针后移一位。

      重复以上步骤,直到c1或c2其中一个指针已移动到数组末端。

      代码实现如下:

     1     public int[] intersect(int[] nums1, int[] nums2) {
     2         int cur1 = 0, cur2 = 0; // 定义指针,指向数组开始位置
     3         List<Integer> list = new ArrayList<>();
     4         while (cur1 < nums1.length && cur2 < nums2.length) { // 循环结束条件:任何一个指针指向对应数组的末端
     5             int num1 = nums1[cur1];
     6             int num2 = nums2[cur2];
     7             if (num1 == num2) { // 重复数字,加入结果列表中
     8                 list.add(num1);
     9                 cur1++;
    10                 cur2++;
    11             } else if (num1 < num2) { // 将cur1指针后移一位,继续下一次比较
    12                 cur1++;
    13             } else { // 将cur2指针后移一位,继续下一次比较
    14                 cur2++;
    15             }
    16         }
    17         int[] result = new int[list.size()];
    18         // 为满足题目返回值类型,将list转换为int数组
    19         for (int i = 0; i < list.size(); i++) {
    20             result[i] = list.get(i);
    21         }
    22         return result;
    23     }
    • 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
      我们来对比上述两种方法:
      假设nums1和nums2的长度为l1, l2。
     
      第一种:
        不考虑结尾转换int数组的循环,一共有两处循环:
        1、第一次循环nums1初始化计数器。
        2、第二次循环nums2与计数器中存储的数值作比较。
        无论如何,这两种循环都需要完全执行,实际循环次数为 l1 + l2。
     
      第二种:
        不考虑结尾转换int数组的循环,一共有一处循环:
        1、每次循环同时在nums1和nums2中取值对比,如果相等,同时移动两个指针,一个指针结束后,循环结束。因为 l1 比 l2 小很多,只需要执行完l1次循环即可,实际消耗时间远远小于第一种方法。
     
        最差的情况:最差的情况是nums1和nums2中完全没有一个重复数字,且nums1中的最后一个元素大于nums2的倒数第二个元素,nums2的最后一个元素大于nums1的倒数第二个元素,在这种情况下,第二种方法的循环也同样需要执行 l1 + l2次。
     
      因此,只有在极端情况下,两种方法效率大约相等,其他任何情况下,第二种方法是要优于第一种方法的。
     
    • 如果nums2的元素存储在磁盘上,内存是有限的,你不能一次加载所有的元素到内存中,你该怎么办?

      如果nums2的元素多到无法一次性加载到内存中,那我们应该:

      1、将nums1中的数字初始化计数器。

      2、使用缓冲流读取文件的一部分数据,计数器中有记录且记录的次数大于1,将该数字新增到结果数组中,计数器中该数字记录的次数减1,这样完成了这一部分数据的统计。

      3、接着再读取文件中下一部分数据,重复步骤2。

      

    OK,以上是这个问题的一些想法,如果朋友们有更好的方式,欢迎留言交流哈~

      

  • 相关阅读:
    codeforces 765 F Souvenirs 线段树+set
    codeforces 768 E 变形NIM博弈/手写sg函数
    BZOJ 1001 狼抓兔子(网络流)
    BZOJ 2957 楼房重建 (分块)
    CodeForces
    CodeForces
    HYSBZ
    SPOJ
    Codeforces-963 D Frequency of String
    中石油2019寒假集训第一场(新生场)(补题)
  • 原文地址:https://www.cnblogs.com/zfLee/p/9332552.html
Copyright © 2011-2022 走看看