兩個數組的交集 II [ LeetCode - 350 ]


 
原題地址: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,以上是這個問題的一些想法,如果朋友們有更好的方式,歡迎留言交流哈~

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM