LCR 061. 查找和最小的 K 对数字
题目描述
给定两个以升序排列的整数数组 nums1 和 nums2,以及一个整数 k。定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2。请找到和最小的 k 个数对 (u1,v1), (u2,v2) ... (uk,vk)。
例如:nums1 = [1,7,11],nums2 = [2,4,6],k = 3,输出 [[1,2],[1,4],[1,6]]。
解释:和最小的三对依次是 (1,2) 和=3,(1,4) 和=5,(1,6) 和=7。
解题思路
暴力法
枚举所有 m * n 个数对,排序后取前 k 个。时间复杂度 O(mn log(mn))。当数组较大时效率很低。
最小堆优化(O(k log k))
核心思想:利用两个数组升序排列的性质,使用最小堆来”多路归并”。
关键观察
对于固定 nums1[i],它与 nums2 中元素配对的和是递增的:
1 | (i,0): nums1[i] + nums2[0] (最小) |
这类似于合并 k 个有序链表。
算法步骤
初始化堆:将每行的最小值放入最小堆,即对于
i = 0..min(nums1.length, k)-1,将(nums1[i] + nums2[0], i, 0)插入堆中。堆按”和”从小到大排序。贪心选取:循环 k 次(或直到堆空):
- 弹出堆顶元素
(sum, i, j),这就是当前最小的数对。 - 将
(nums1[i], nums2[j])加入结果集。 - 如果
j + 1 < nums2.length,将下一列的元素(nums1[i] + nums2[j+1], i, j+1)插入堆中(按行指针后移)。
- 弹出堆顶元素
返回结果集。
示例说明
以 nums1 = [1,7,11],nums2 = [2,4,6],k = 3 为例:
初始化堆:插入 (1+2=3, i=0, j=0), (7+2=9, i=1, j=0), (11+2=13, i=2, j=0) → 堆:[3, 9, 13]
- 第 1 轮:弹出 (3,0,0),加入 [1,2];插入 (1+4=5, 0, 1) → 堆:[5, 9, 13]
- 第 2 轮:弹出 (5,0,1),加入 [1,4];插入 (1+6=7, 0, 2) → 堆:[7, 9, 13]
- 第 3 轮:弹出 (7,0,2),加入 [1,6]。k 轮完成。
- 结果:
[[1,2],[1,4],[1,6]]
为什么高效?
对于一个加法表,我们只需要维护最多 min(m, k) 行的最小值。每次弹出一个元素,只向堆中插入该行的下一个元素。总的时间复杂度为 O(k log min(m, k)),远优于 O(mn log(mn))。
复杂度分析
- 时间复杂度:
O(k log min(m, k)),其中 m = nums1.length。每次堆操作 O(log m),共 k 次。 - 空间复杂度:
O(min(m, k)),堆中最多存储 min(m, k) 个元素。
代码
1 | package lcr.J061; |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 𝒞𝒶𝓃𝒶𝓇𝓎!