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