【中等】力扣算法题解析LeetCode373:查找和最小的 K 对数字

关注文末推广名片,即可免费获得本题测试源码

题目来源:LeetCode 373. 查找和最小的 K 对数字

问题抽象: 给定两个 升序排列 的整数数组 nums1nums2 及一个整数 k,要求找出所有可能的数对 (u, v)u 来自 nums1v 来自 nums2)中,和最小的前 k 个数对,满足以下核心需求:

  1. 数对定义与排序

    • 每个数对 (u, v) 的权重为其元素和 u + v
    • 输出需按 和值升序排列(和相同时数对顺序不限)。
  2. 计算约束

    • 时间复杂度 O(k log k)(避免生成全部 n1×n2 数对),空间复杂度 O(k)
    • 需用 最小堆 动态维护候选数对:
      • 初始化:将 nums1 的每个元素与 nums2 首元素组成数对入堆;
      • 每次弹出堆顶数对 (i, j),并添加同 i 的下一个 j+1 数对(若存在)。
  3. 去重与边界

    • 数对 允许值重复(如 (1,1) 可多次出现),但需确保索引组合唯一;
    • 特殊输入:
      • 任一数组为空 → 返回空列表;
      • k=0 → 返回空列表;
      • k > 数对总数 → 返回全部数对(升序);
    • 大数处理:数组长度 n1, n2 ≤ 10^4k ≤ 10^4

输入:数组 nums1(长度 n1 ≥ 0),数组 nums2(长度 n2 ≥ 0),整数 k0 ≤ k ≤ 10^4)。
输出:和最小的前 k 个数对列表(格式 List[List[int]])。


解题思路

题目要求从两个递增数组 nums1nums2 中找出和最小的 k 个数对(每个数对包含两个数组中的一个元素)。解题的核心在于利用最小堆(优先队列)高效地动态获取当前最小和数对,并通过逐步扩展候选数对来保证正确性。具体步骤如下:

  1. 初始化最小堆

    • 堆中存储两个数组的下标对 [i, j],排序规则为 nums1[i] + nums2[j] 的值从小到大。
    • 初始时,将 nums1 中每个元素与 nums2 的第一个元素配对(即 [i, 0])加入堆中,因为 nums2 是递增的,这些数对是每个 i 对应的最小和候选。
  2. 动态获取最小和数对

    • 从堆顶弹出当前最小和数对 [i, j],加入结果列表。
    • 如果当前数对的 j 下标未达到 nums2 的末尾,则扩展新的候选数对:固定 i,将 j+1 加入堆中(因为 nums2 递增,(i, j+1) 是当前 i 的下一个最小和候选)。
  3. 终止条件

    • 重复上述过程直到取满 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;
    }
}

代码说明

  1. 边界处理
    当任一数组为空或 k <= 0 时直接返回空列表,避免无效操作。

  2. 最小堆初始化

    • 使用 PriorityQueue 并自定义比较器:比较规则为 nums1[i] + nums2[j] 的和升序。
    • 初始时每个 nums1[i] 固定配对 nums2[0],形成初始候选集。
  3. 动态扩展候选

    • 每次弹出堆顶(当前最小和数对)后,检查该数对在 nums2 中是否还有下一个元素(j + 1 < len2)。若有,则将 (i, j+1) 加入堆中。这样能保证每次添加的都是当前最小和的有效扩展。
  4. 高效性

    • 堆的大小始终不超过 nums1 的长度(每个 i 最多只有一个候选在堆中),空间优化到 O(n)。
    • 每次堆操作 O(log n),总时间复杂度 O(n + k log n),满足性能要求。

提交详情(执行用时、内存消耗)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值