文章目錄
- 題目描述
- 解題思路
- 代碼
題目鏈接
題目描述
給定兩個以 非遞減順序排列 的整數數組 nums1 和 nums2 , 以及一個整數 k 。
定義一對值 (u,v),其中第一個元素來自 nums1,第二個元素來自 nums2 。
請找到和最小的 k 個數對 (u1,v1), (u2,v2) … (uk,vk) 。
示例 1:
輸入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
輸出: [1,2],[1,4],[1,6]
解釋: 返回序列中的前 3 對數:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
示例 2:
輸入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
輸出: [1,1],[1,1]
解釋: 返回序列中的前 2 對數:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
提示:
1 <= nums1.length, nums2.length <= 105
-109 <= nums1[i], nums2[i] <= 109
nums1 和 nums2 均為 升序排列
1 <= k <= 104
k <= nums1.length * nums2.length
解題思路
參考
多路歸并的方法來解決這個問題,因為我們是找前k個最小的數,那么我們可以這樣來,
令 nums1 的長度為 n,nums2 的長度為 m,所有的點對數量為 n×m。
其中每個 nums1[i] 參與所組成的點序列為:
[(nums1[0],nums2[0]),(nums1[0],nums2[1]),…,(nums1[0],nums2[m?1])]
[(nums1[1],nums2[0]),(nums1[1],nums2[1]),…,(nums1[1],nums2[m?1])]
[(nums1[n?1],nums2[0]),(nums1[n?1],nums2[1]),…,(nums1[n?1],nums2[m?1])]
由于 nums1 和 nums2 均已按升序排序,因此每個 nums1[i] 參與構成的點序列也為升序排序,這引導我們使用「多路歸并」來進行求解。
怎么做呢?
既然是多路排序,那就是把以前的二路排序擴展一下,現在我們使用n路排序,我們按照上面的分法,就可以把序列分成n行,然后,我們可以在這n行中每次選最小的一個就好啦,這樣選k次,就是我們要的答案了。
具體怎么實現呢?
我們其實的時候買把這n個序列的第一個元素(以二元組(i,j))入隊(優先隊列,或者是小根堆),其中 i 為該點對中 nums1[i] 的下標,j 為該點對中 nums2[j]的下標,這里可以有一個小優化,我們始終確保nums1為兩數組中長度較小的那個,然后通過標記來記錄是否發生過交換,確保答案的點順序的正確性。
每次從優先隊列中取出堆頂元素(這個堆頂就是當前未被加入到答案的所有點對中的最小值)加入答案,并將該點對所在序列的下一位(如果有的話)加入到優先隊列。
代碼
class Solution {boolean flag = true;public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {int n = nums1.length, m = nums2.length;if(n > m && !(flag = false))return kSmallestPairs(nums2, nums1, k);List<List<Integer>> ans = new ArrayList<>();PriorityQueue<int[]> q = new PriorityQueue<>((a,b) -> (nums1[a[0]] + nums2[a[1]]) - (nums1[b[0]] + nums2[b[1]]));// 如果n>k的話,那我們其實只需要建立nums1的前k個序對就夠了for(int i=0; i<Math.min(n, k) ;i++){q.add(new int[]{i,0});}while(ans.size() < k && !q.isEmpty()){int[] poll = q.poll();int a = poll[0], b= poll[1];ans.add(new ArrayList<>(){{add(flag ? nums1[a] : nums2[b]);add(flag ? nums2[b] : nums1[a]);}});if(b+1 < m)q.add(new int[]{a, b+1});}return ans;}
}