题目来源:LeetCode 373. 查找和最小的 K 对数字
问题抽象: 给定两个 升序排列 的整数数组 nums1
和 nums2
及一个整数 k
,要求找出所有可能的数对 (u, v)
(u
来自 nums1
,v
来自 nums2
)中,和最小的前 k
个数对,满足以下核心需求:
-
数对定义与排序:
- 每个数对
(u, v)
的权重为其元素和u + v
; - 输出需按 和值升序排列(和相同时数对顺序不限)。
- 每个数对
-
计算约束:
- 时间复杂度 O(k log k)(避免生成全部
n1×n2
数对),空间复杂度 O(k); - 需用 最小堆 动态维护候选数对:
- 初始化:将
nums1
的每个元素与nums2
首元素组成数对入堆; - 每次弹出堆顶数对
(i, j)
,并添加同i
的下一个j+1
数对(若存在)。
- 初始化:将
- 时间复杂度 O(k log k)(避免生成全部
-
去重与边界:
- 数对 允许值重复(如
(1,1)
可多次出现),但需确保索引组合唯一; - 特殊输入:
- 任一数组为空 → 返回空列表;
k=0
→ 返回空列表;k > 数对总数
→ 返回全部数对(升序);
- 大数处理:数组长度
n1, n2 ≤ 10^4
,k ≤ 10^4
。
- 数对 允许值重复(如
输入:数组 nums1
(长度 n1 ≥ 0
),数组 nums2
(长度 n2 ≥ 0
),整数 k
(0 ≤ k ≤ 10^4
)。
输出:和最小的前 k
个数对列表(格式 List[List[int]]
)。
解题思路
题目要求从两个递增数组 nums1
和 nums2
中找出和最小的 k 个数对(每个数对包含两个数组中的一个元素)。解题的核心在于利用最小堆(优先队列)高效地动态获取当前最小和数对,并通过逐步扩展候选数对来保证正确性。具体步骤如下:
-
初始化最小堆:
- 堆中存储两个数组的下标对
[i, j]
,排序规则为nums1[i] + nums2[j]
的值从小到大。 - 初始时,将
nums1
中每个元素与nums2
的第一个元素配对(即[i, 0]
)加入堆中,因为nums2
是递增的,这些数对是每个i
对应的最小和候选。
- 堆中存储两个数组的下标对
-
动态获取最小和数对:
- 从堆顶弹出当前最小和数对
[i, j]
,加入结果列表。 - 如果当前数对的
j
下标未达到nums2
的末尾,则扩展新的候选数对:固定i
,将j+1
加入堆中(因为nums2
递增,(i, j+1)
是当前i
的下一个最小和候选)。
- 从堆顶弹出当前最小和数对
-
终止条件:
- 重复上述过程直到取满 k 个数对,或堆为空(所有候选已处理完)。
时间复杂度:初始化堆 O(n),每次堆操作 O(log n),共执行 k 次,整体 O(n + k log n)。 空间复杂度:堆最多存储 O(n) 个元素(n 为 nums1
长度)。
代码实现(Java版)🔥点击下载源码
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
List<List<Integer>> res = new ArrayList<>();
// 处理边界情况:数组为空或k非正
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0 || k <= 0) {
return res;
}
// 最小堆:按 nums1[i] + nums2[j] 升序排列,存储下标对 [i, j]
PriorityQueue<int[]> heap = new PriorityQueue<>(
(a, b) -> nums1[a[0]] + nums2[a[1]] - (nums1[b[0]] + nums2[b[1]])
);
int len1 = nums1.length;
int len2 = nums2.length;
// 初始化:将 nums1 中每个元素与 nums2[0] 配对入堆
for (int i = 0; i < len1; i++) {
heap.offer(new int[]{i, 0});
}
// 取 k 个最小和数对
while (k-- > 0 && !heap.isEmpty()) {
int[] indices = heap.poll();
int i = indices[0];
int j = indices[1];
// 将数对加入结果
res.add(Arrays.asList(nums1[i], nums2[j]));
// 扩展候选:若 nums2 还有下一个元素,则加入新数对 (i, j+1)
if (j + 1 < len2) {
heap.offer(new int[]{i, j + 1});
}
}
return res;
}
}
代码说明
-
边界处理:
当任一数组为空或k <= 0
时直接返回空列表,避免无效操作。 -
最小堆初始化:
- 使用
PriorityQueue
并自定义比较器:比较规则为nums1[i] + nums2[j]
的和升序。 - 初始时每个
nums1[i]
固定配对nums2[0]
,形成初始候选集。
- 使用
-
动态扩展候选:
- 每次弹出堆顶(当前最小和数对)后,检查该数对在
nums2
中是否还有下一个元素(j + 1 < len2
)。若有,则将(i, j+1)
加入堆中。这样能保证每次添加的都是当前最小和的有效扩展。
- 每次弹出堆顶(当前最小和数对)后,检查该数对在
-
高效性:
- 堆的大小始终不超过
nums1
的长度(每个i
最多只有一个候选在堆中),空间优化到 O(n)。 - 每次堆操作 O(log n),总时间复杂度 O(n + k log n),满足性能要求。
- 堆的大小始终不超过