一、题目描述
You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k.
Define a pair (u,v) which consists of one element from the first array and one element from the second array.
Find the k pairs (u1,v1),(u2,v2) ...(uk,vk) with the smallest sums.
Input: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
Output: [[1,2],[1,4],[1,6]]
Explanation: The first 3 pairs are returned from the sequence:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
二、题解
private int twoSum(int[] arr) {
return arr[0] + arr[1];
}
方法一:最小堆 + 穷举
算法
最普遍的做法就是组合 nums1 与 nums2 的所有数字;然后,将所有组合按照组合数之和降序存放在队列中,形成一个最小堆;最后将前 k 项组合添加进列表中。
/**
* @date: 2/21/2020 8:41 PM
* @Execution info:296ms,5.07%,29.84%
*/
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
LinkedList<List<Integer>> resList = new LinkedList<>();
PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));
for (int n1 : nums1)
for (int n2 : nums2) {
int[] arr = new int[] {n1, n2};
pQ.add(arr);
if (pQ.size() > k)
pQ.poll();
}
while (!pQ.isEmpty()) {
LinkedList<Integer> list = new LinkedList<>();
int[] arr = pQ.poll();
list.add(arr[0]); list.add(arr[1]);
resList.add(list);
}
return resList;
}
复杂度分析
- 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
- 空间复杂度:
方法二:最小堆 + 剪枝
对当队列已经足够 k 个时,如果有新的数字对要进队,应该先与队头元素进行比较,如果不小于队头元素的和,就没必要进队;否则,将队头元素出队,将这地数入队。
/**
* @date: 2/21/2020 8:45 PM
* @Execution info:184ms,8.07%,31.42%
*/
public List<List<Integer>> kSmallestPairs1(int[] nums1, int[] nums2, int k) {
LinkedList<List<Integer>> resList = new LinkedList<>();
PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));
for (int n1 : nums1)
for (int n2 : nums2) {
int[] arr = new int[] {n1, n2};
if (pQ.size() < k)
pQ.add(arr);
else if (pQ.size() >= k && twoSum(arr) < twoSum(pQ.peek())) {
pQ.poll();
pQ.add(arr);
}
}
while (!pQ.isEmpty()) {
LinkedList<Integer> list = new LinkedList<>();
int[] arr = pQ.poll();
list.add(arr[0]);
list.add(arr[1]);
resList.add(list);
}
return resList;
}
复杂度分析
- 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
- 空间复杂度:
方法三:最小堆 + 双剪枝
这里在继承方法二的剪枝的前提下,继续优化代码:
由于数组 nums1,nums2 都是有序的,又因为某一对值 (u, v) 中的 u 一定来自 nums1,v 一定来自 nums2。
我们可以这样做,当前 n1,n2 均大于上一次(queue 满 k 个之后)的添加数字,直接退出本次内层循环。因为队列满了,就证明我已经拿够了 k 对和最小的数字了,没必要再添加更大的。
/**
* @date: 2/21/2020 8:54 PM
* @Execution info:24ms,58.35%,31.74%
*/
public List<List<Integer>> kSmallestPairs2(int[] nums1, int[] nums2, int k) {
ArrayList<List<Integer>> resList = new ArrayList<>();
PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));
int pre_n1 = 0x7fffffff;
int pre_n2 = 0x7fffffff;
for (int n1 : nums1)
for (int n2 : nums2) {
if (n1 > pre_n1 && n2 > pre_n2) {
break;
}
int[] arr = new int[] {n1, n2};
if (pQ.size() < k) {
pQ.add(arr);
} else if (pQ.size() >= k && twoSum(arr) < twoSum(pQ.peek())) {
pQ.poll();
pQ.add(arr);
pre_n1 = n1;
pre_n2 = n2;
}
}
while (!pQ.isEmpty()) {
LinkedList<Integer> list = new LinkedList<>();
int[] arr = pQ.poll();
list.add(arr[0]);
list.add(arr[1]);
resList.add(list);
}
return resList;
}
复杂度分析
- 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
- 空间复杂度: