leetcode
leetcode 301 ~ 350
查找和最小的 K 对数字

查找和最小的 K 对数字

难度:

标签:

题目描述

给定两个以 非递减顺序排列 的整数数组 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]

 

提示:

  • 1 <= nums1.length, nums2.length <= 105
  • -109 <= nums1[i], nums2[i] <= 109
  • nums1nums2 均为 升序排列
  • 1 <= k <= 104
  • k <= nums1.length * nums2.length

代码结果

运行时间: 95 ms, 内存: 32.1 MB


/*
 * 思路:
 * 1. 利用 Stream API 的 flatMap 方法将两个数组合并成数对并求和。
 * 2. 使用 sorted 方法对数对按和进行排序。
 * 3. 使用 limit 方法获取前 k 个数对。
 */
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class Solution {
    public List<int[]> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        return IntStream.range(0, nums1.length)
                .boxed()
                .flatMap(i -> IntStream.range(0, nums2.length)
                        .mapToObj(j -> new int[]{nums1[i], nums2[j]}))
                .sorted(Comparator.comparingInt(pair -> pair[0] + pair[1]))
                .limit(k)
                .collect(Collectors.toList());
    }
}
 

解释

方法:

这个题解的思路是使用小根堆来维护当前最小的k个数对。首先将(nums1[0]+nums2[0], 0, 0)放入小根堆中,表示当前最小的数对。然后进行k次循环,每次从堆顶弹出当前最小的数对,并将其加入答案数组。接着,如果该数对的下标j等于0,且i+1

时间复杂度:

O(klogk)

空间复杂度:

O(k)

代码细节讲解

🦆
为什么在初始化小根堆时,只将(nums1[0]+nums2[0], 0, 0)这一对元素加入,而不是从每个数组中各取一个元素加入?
初始化小根堆时只加入(nums1[0]+nums2[0], 0, 0)是为了从最小可能的数对开始,并逐步扩展探索其他可能的数对。这种方法保证了算法的效率和简洁性,避免了一开始就加载过多的数对可能导致的冗余计算。从(nums1[0],nums2[0])开始,我们可以确保每次只扩展当前已知的最小数对,从而逐步覆盖所有可能的最小数对组合。
🦆
在添加新元素到小根堆时,为什么只在j等于0时检查并添加(nums1[i+1]+nums2[j], i+1, j),这样做有什么特别的原因吗?
这样做是为了保证不会重复添加相同的数对组合。当我们从堆中取出一个元素(nums1[i], nums2[j])时,我们知道在(nums1[i], nums2[j+1])之前,所有的(nums1[i], nums2[x]) (其中x < j)已经被处理过。类似地,我们只在j为0时添加(nums1[i+1], nums2[j]),是因为这表示i行的开始,保证了每一对nums1中的元素与nums2中第一个元素的组合都会被考虑,而不会重复添加之前已经加入堆的组合。
🦆
在从堆中弹出元素后,为什么要分别检查并添加(nums1[i+1]+nums2[j], i+1, j)和(nums1[i]+nums2[j+1], i, j+1),这两者有什么区别?
这两种添加方式代表不同的探索方向。将(nums1[i+1]+nums2[j], i+1, j)添加到堆中是向下探索,即在固定列j的情况下,探索下一个行元素。而将(nums1[i]+nums2[j+1], i, j+1)添加到堆中是向右探索,即在固定行i的情况下,探索下一个列元素。这样做可以确保不遗漏任何可能的数对组合,同时避免重复添加相同的数对。
🦆
在实际操作中,如果k值非常大,超过了所有可能的数对组合的数量,该算法如何处理?
如果k值超过了所有可能的数对组合的数量,算法会继续执行,直到堆中没有更多元素可弹出为止。在这种情况下,算法将返回所有可能的数对组合,但总数会小于k。因此,算法的输出将是实际可能的数对的最大数量,而不是k个数对。

相关问题

有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k不同 的元素。

你必须找到一个内存复杂度优于 O(n2) 的解决方案。

 

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

 

提示:

  • n == matrix.length
  • n == matrix[i].length
  • 1 <= n <= 300
  • -109 <= matrix[i][j] <= 109
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n2

 

进阶:

  • 你能否用一个恒定的内存(即 O(1) 内存复杂度)来解决这个问题?
  • 你能在 O(n) 的时间复杂度下解决这个问题吗?这个方法对于面试来说可能太超前了,但是你会发现阅读这篇文章( this paper )很有趣。

找出第 K 小的数对距离

数对 (a,b) 由整数 ab 组成,其数对距离定义为 ab 的绝对差值。

给你一个整数数组 nums 和一个整数 k ,数对由 nums[i]nums[j] 组成且满足 0 <= i < j < nums.length 。返回 所有数对距离中k 小的数对距离。

 

示例 1:

输入:nums = [1,3,1], k = 1
输出:0
解释:数对和对应的距离如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
距离第 1 小的数对是 (1,1) ,距离为 0 。

示例 2:

输入:nums = [1,1,1], k = 2
输出:0

示例 3:

输入:nums = [1,6,1], k = 3
输出:5

 

提示:

  • n == nums.length
  • 2 <= n <= 104
  • 0 <= nums[i] <= 106
  • 1 <= k <= n * (n - 1) / 2