leetcode
leetcode 351 ~ 400
有序矩阵中第 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 )很有趣。

代码结果

运行时间: 24 ms, 内存: 19.5 MB


/*
 * 思路:
 * 使用Java Stream实现,先将矩阵展平成一个一维数组,
 * 然后排序并取出第k小的元素。
 */
import java.util.Arrays;
import java.util.stream.Stream;
 
public class KthSmallestElementStream { 
    public int kthSmallest(int[][] matrix, int k) { 
        return Stream.of(matrix)
                     .flatMapToInt(Arrays::stream)
                     .sorted()
                     .skip(k - 1)
                     .findFirst()
                     .getAsInt(); 
    }
}

解释

方法:

这个题解采用了二分查找的方法。首先确定查找的范围是矩阵的最小值matrix[0][0]到最大值matrix[n-1][n-1]。然后在这个范围内进行二分查找,每次查找都通过check函数来判断矩阵中小于等于mid的元素个数是否大于等于k。如果是,说明第k小的元素在左半部分,否则在右半部分。最终当left和right相遇时,left即为所求的第k小的元素。

时间复杂度:

O(nlog(matrix[n-1][n-1] - matrix[0][0]))

空间复杂度:

O(1)

代码细节讲解

🦆
在二分查找中,为何选择矩阵的左上角最小值和右下角最大值作为初始的查找范围?
在二分查找中,选择左上角最小值和右下角最大值作为初始查找范围是因为在一个有序矩阵中,左上角的元素是整个矩阵中最小的,而右下角的元素是最大的。这样的选择确保了覆盖所有可能的值,从而能够通过二分查找有效地缩小查找范围来找到第K小的元素。
🦆
check函数中通过从左下角到右上角遍历实现计数的逻辑是基于什么矩阵特性?
check函数中的遍历逻辑是基于矩阵的行和列都是递增排序的特性。从左下角开始遍历(即最底行的最左列),可以利用这一特性有效地计数。向右移动(增加列)时,元素递增,向上移动(减少行)时,元素递减。这样可以根据当前元素与mid的比较,快速地计算出矩阵中小于等于mid的元素数量,从而判断是否满足条件。
🦆
二分查找过程中,如果矩阵中存在多个相同的元素,check函数如何保证正确统计小于等于mid的元素个数?
check函数通过从左下角到右上角的遍历方式,确保了所有小于等于mid的元素都被计入计数。即使矩阵中存在多个相同的元素,当这些元素小于或等于mid时,它们都会在遍历过程中被统计。每次向右移动都会增加当前行的全部元素(直到该行当前列的元素大于mid),因此所有小于等于mid的重复元素都会被正确统计。
🦆
在二分查找的每一步,当确定mid值后,为何选择将right更新为mid而不是mid-1当check(mid)返回true?
在二分查找中,当check(mid)返回true,表示矩阵中小于等于mid的元素个数至少是k,因此mid有可能是第k小的元素。将right设置为mid而不是mid-1是为了确保不错过这个可能的解。如果设置为mid-1,可能会将实际的第k小元素排除在外。在最终的循环结束时,left和right会相遇在第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

乘法表中第k小的数

几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第 k 小的数字吗?

乘法表是大小为 m x n 的一个整数矩阵,其中 mat[i][j] == i * j(下标从 1 开始)。

给你三个整数 mnk,请你在大小为 m x n 的乘法表中,找出并返回第 k 小的数字。

 

示例 1:

输入:m = 3, n = 3, k = 5
输出:3
解释:第 5 小的数字是 3 。

示例 2:

输入:m = 2, n = 3, k = 6
输出:6
解释:第 6 小的数字是 6 。

 

提示:

  • 1 <= m, n <= 3 * 104
  • 1 <= k <= m * n

找出第 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

第 K 个最小的质数分数

给你一个按递增顺序排序的数组 arr 和一个整数 k 。数组 arr1 和若干 质数 组成,且其中所有整数互不相同。

对于每对满足 0 <= i < j < arr.lengthij ,可以得到分数 arr[i] / arr[j]

那么第 k 个最小的分数是多少呢?  以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j]

 

示例 1:

输入:arr = [1,2,3,5], k = 3
输出:[2,5]
解释:已构造好的分数,排序后如下所示: 
1/5, 1/3, 2/5, 1/2, 3/5, 2/3
很明显第三个最小的分数是 2/5

示例 2:

输入:arr = [1,7], k = 1
输出:[1,7]

 

提示:

  • 2 <= arr.length <= 1000
  • 1 <= arr[i] <= 3 * 104
  • arr[0] == 1
  • arr[i] 是一个 质数i > 0
  • arr 中的所有数字 互不相同 ,且按 严格递增 排序
  • 1 <= k <= arr.length * (arr.length - 1) / 2

 

进阶:你可以设计并实现时间复杂度小于 O(n2) 的算法解决此问题吗?