「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
leetcode-373 查找和最小的 K 对数字
b站视频
题目介绍
给定两个以 升序排列 的整数数组 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^9nums1和nums2均为升序排列1 <= k <= 1000
解题思路
此题是求最值的问题,那么就可以用堆来解决,关键是用什么堆?
因为要求和最小的 K 对数字,所以可以使用一个大小为 K 的大顶堆来保存和最小的 K 对数字,此时这 K 对数字中和最大的数字对在堆顶的位置,所以之后的数字对只需要和堆顶的数字对进行比较即可,如果比堆顶的数字对的和小,那么就可以替换掉堆顶的数字对进入堆中,否则就不给进入堆中
解题步骤
- 创建一个大小为
K的大顶堆 - 用双重
for循环将两个数组的所有数对push到大顶堆中 - 如果当前堆的大小小于
K,那么直接往堆尾插入数对,然后向上调整大顶堆的结构 - 如果当前堆的大小等于
K,那么比较当前堆顶数对的和是否比新的数对的和大,如果是,新的数对替换当前堆顶的数对,然后向下调整大顶堆的结构;否则新的数对无法进入大顶堆中 - 当所有数对全部插入完毕,返回此时堆中的所有数对即为和最小的
K对数字
解题代码
构建大顶堆
大顶堆需要具有 push(插入数对)、top(返回堆顶的数对)、size(堆的大小)、sortBack(向上调整堆结构)、sortFront(向下调整堆结构)、getHeap(返回堆中的元素)这几个方法
class Heap {
constructor(k) {
this.heap = []
this.k = k
}
getHeap() {
return this.heap
}
size() {
return this.heap.length
}
top() {
return this.heap[0]
}
push(val) {
if (this.size() < this.k) {
this.heap.push(val)
this.sortBack()
} else if (compare(val, this.heap[0]) < 0) {
this.heap[0] = val
this.sortFront()
}
}
sortBack() {
let i = this.heap.length - 1
while (i > 0 && compare(this.heap[i], this.heap[Math.floor((i - 1) / 2)]) > 0) {
[this.heap[i], this.heap[Math.floor((i - 1) / 2)]] = [this.heap[Math.floor((i - 1) / 2)], this.heap[i]]
i = Math.floor((i - 1) / 2)
}
}
sortFront() {
let i = 0
while (i * 2 + 1 < this.heap.length) {
let temp = i
if (compare(this.heap[temp], this.heap[i * 2 + 1]) < 0) temp = i * 2 + 1
if (i * 2 + 2 < this.heap.length && compare(this.heap[temp], this.heap[i * 2 + 2]) < 0) temp = i * 2 + 2
if (temp === i) break
[this.heap[temp], this.heap[i]] = [this.heap[i], this.heap[temp]]
i = temp
}
}
}
比较方法
需要比较两个数对之间和的大小关系,因此还需要一个比较方法 compare
var compare = function(a, b) {
return a[0] + a[1] - b[0] - b[1]
}
遍历 nums1 和 nums2 两个数组中的所有数对插入到堆中
var kSmallestPairs = function(nums1, nums2, k) {
const heap = new Heap(k)
for (let i = 0; i < nums1.length; i++) {
for (let j = 0; j < nums2.length; j++) {
heap.push([nums1[i], nums2[j]])
}
}
return heap.getHeap()
};
按照这样的解题方法,如果数据量过大会出现超出时间限制的报错
优化思路
超出时间限制是因为我们没有利用好题目的条件,题目已经说明了这两个数组是 升序排序 的数组,因此假设当 [nums1[i], nums2[j] 这个数组已经大于等于堆顶的数对之和,那么说明 nums1[i], nums2[j + 1] 以及其之后的所有数对之和都将大于堆顶数对之和,因此我们可以不需要遍历之后的数对了,直接结束当前循环即可
优化方法
在遍历数对并将数对插入到堆中的过程中,我们可以做以下的判断:
- 如果当前堆的大小等于
K,判断当前的数对是否大于堆顶的数对之和,如果是,退出当前的循环,不需要再遍历 数组nums2之后的元素
优化代码
var kSmallestPairs = function(nums1, nums2, k) {
const heap = new Heap(k)
for (let i = 0; i < nums1.length; i++) {
for (let j = 0; j < nums2.length; j++) {
if (heap.size() < k) {
heap.push([nums1[i], nums2[j]])
} else {
// 如果待插入数对的和大于堆顶数对之和,退出当前循环
if (compare([nums1[i], nums2[j]], heap.top()) >= 0) break
heap.push([nums1[i], nums2[j]])
}
}
}
return heap.getHeap()
};
如此优化之后,顺利解决该问题