题目
给定两个以升序排列的整数数组 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]
示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]
提示:
1 <= nums1.length, nums2.length <= 10^5
-10^9 <= nums1[i], nums2[i] <= 10^9
nums1 和 nums2 均为升序排列
1 <= k <= 1000
思路
先将所有可能的组合对(u,v)筛选出来。外层循环取u——第一个元素;内层循环取v——第二个元素。
用一个大小为k的最大堆存储组合对,即优先级队列中存储类的对象(比存数组好用)。
k的大小和数组长度没有直接关系,需要判断边界。
示例 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]
此时k == nums1.length || nums2.length,当然要把整个数组遍历完。在n个升序元素中,取前n个元素,当然要整个遍历。
示例 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]
此时k < nums1.length || nums2.length,遍历数组只需走k步即可。要从n个升序数组中,取前k个最小值,只需要从第一个元素开始走k步即可。
此处的数组遍历是双重循环,u-来自于nums1,v-来自于nums2。要把nums1和nums2都走一遍。
nums1 = [1,1,2]:u,k < nums1.length,因此2这个值肯定用不到,最多走到第2个1就结束。
nums2 = [1,2,3]:v,k < nums2.length,因此3这个值肯定用不到,最多走到第2个2就结束。
拿着nums1数组中的第一个1去遍历nums2数组中的[1,2],得到[1,1],[1,2]。
拿着nums1数组中的第二个1去遍历nums2数组中的[1,2],得到[1,1],[1,2]。
在最终得到的[1,1],[1,2],[1,1],[1,2]四个集合中选出前2个和最小的组合,得到[1,1],[1,1]。
示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]
此时k > nums1.length || nums2.length,排列组合的个数
总结:
以上三种情况,我们在遍历数组时不是直接遍历结束,而是走Math.min(k, nums.length)。
代码
class Solution {
//此时Pair类就具备可比较能力
private class Pair implements Comparable {
//第一个数组元素
int u;
//第二个数组元素
int v;
//构造方法
public Pair(int u, int v) {
this.u = u;
this.v = v;
}
@Override
public int compareTo(Pair o) {
//取小用大,将JDK内置的最小堆转化为最大堆
return (o.u + o.v) - (this.u + this.v);
}
}
public List> kSmallestPairs(int[] nums1, int[] nums2, int k) {
Queue queue = new PriorityQueue<>();
//最外层取u值
for (int i = 0; i < Math.min(nums1.length, k); i++) {
//最内层取v值
for (int j = 0; j < Math.min(nums2.length, k); j++) {
if(queue.size() < k) {
queue.offer(new Pair(nums1[i], nums2[j]));
} else {
Pair pair = queue.peek();
if (nums1[i] + nums2[j] < (pair.u + pair.v)) {
queue.poll();
queue.offer(new Pair(nums1[i], nums2[j]));
}
}
}
}
//队列中就存储了前k个最小的pair对象
//List套List,二维数组
List> ret = new ArrayList<>();
//边界条件 k > queue.size();
for (int i = 0; i < k && !(queue.isEmpty()); i++) {
List temp = new ArrayList<>();
Pair pair = queue.poll();
temp.add(pair.u);
temp.add(pair.v);
ret.add(temp);
}
return ret;
}
}
承上启下:
- 二叉树:理论基础,无实际用处,做个题。
- 二分搜索树:TreeSet、TreeMap、红黑树、二分平衡搜索树。(在Java标准库中)
- 哈希表:HashSet、HashMap。(在Java标准库中)
- Set:存储不重复元素的线性表。若只是判定元素是否存在,或者过滤重复元素,使用Set集合。
- Map:存储key-value键值对。存储的数据是一种映射关系,需要根据不重复的key对应value,则需要使用Map集合。(例:身份证号=姓名)
- Set和Map是一种专门用来进行搜索的容器/数据结构,其搜素的效率与其具体的实例化子类有关。
- 尽量不要用Map和Set集合进行遍历,对于这俩集合的遍历操作,是效率比较低的操作。使用Set和Map集合最核心的操作是搜索。