在问 · 算法题解

378. 有序矩阵中第 K 小的元素

中等二分查找值域二分 + 阶梯计数力扣原题 ↗

题目描述

输入一个 n x n 的矩阵 matrix,每一行从左到右、每一列从上到下都是非递减的。另给一个整数 k。

把矩阵所有元素排成一列从小到大排序,返回排在第 k 位的元素(k 从 1 开始数)。值相同的元素各占一个位置,不是求第 k 个不同的值。

要求使用的内存少于 O(n^2)。

示例 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 == matrix[i].length
  • 1 <= n <= 300
  • -10^9 <= matrix[i][j] <= 10^9
  • 每行、每列都按非递减顺序排列
  • 1 <= k <= n^2

进阶:能否只用 O(1) 的额外内存?能否做到 O(n) 时间?后者较难,可参考相关论文。

解题思路:值域二分 + 阶梯计数

对“值”二分,而不是对位置二分。答案在 [左上角, 右下角] 之间。对候选值 mid,数一数矩阵里有多少个数 <= mid:从右上角出发,当前格 > mid 就左移,否则这一行左边的 j + 1 个都 <= mid,计数后下移一行。计数 >= k 说明答案 <= mid,hi = mid;否则 lo = mid + 1。lo == hi 时就是答案,而且它一定是矩阵里的某个数。

为什么正确

第 k 小的元素始终在值区间 [lo, hi] 内;count(x) 随 x 单调不减。

复杂度分析

时间 O(n · log(max - min)),每次计数沿阶梯走 O(n) 步;空间 O(1)。

易错点

计数 >= k 时不能写 hi = mid - 1,mid 本身可能就是答案。值可以为负,C++/Java 里 (lo + hi) / 2 向 0 取整,lo=-3、hi=-2 时 mid=-2 会死循环,要用向下取整(右移)并防止 lo + hi 溢出。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        n = len(matrix)
        lo, hi = matrix[0][0], matrix[n - 1][n - 1]
        while lo < hi:
            mid = (lo + hi) // 2
            cnt, j = 0, n - 1
            for i in range(n):
                while j >= 0 and matrix[i][j] > mid:
                    j -= 1
                cnt += j + 1
            if cnt >= k:
                hi = mid
            else:
                lo = mid + 1
        return lo

载入编辑器,自己跑一遍 →

其他解法

直接排序不利用有序性;最小堆只利用行有序,代价与 k 相关;主解法同时利用行列有序,在值域上二分,空间 O(1)。

解法 2:最小堆多路归并 时间 O(k log n),空间 O(n)

每一行都是一个有序序列,问题变成合并 n 个有序序列后的第 k 个数。先把每行第一个元素和它的行列号放入最小堆。弹出 k - 1 次,每次把被弹出元素同一行的下一个元素入堆。此时堆顶就是答案。

只利用了行有序,思路通用,可直接用于合并 k 个有序链表一类题。k 接近 n² 时比主解法慢,并且需要 O(n) 的堆空间。

class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        n = len(matrix)
        heap = [(matrix[i][0], i, 0) for i in range(min(n, k))]
        heapq.heapify(heap)
        for _ in range(k - 1):
            v, i, j = heapq.heappop(heap)
            if j + 1 < n:
                heapq.heappush(heap, (matrix[i][j + 1], i, j + 1))
        return heap[0][0]

解法 3:直接排序 时间 O(n² log n),空间 O(n²)

把矩阵所有元素放进一个一维数组。排序后返回下标 k - 1 的元素。

实现最简单,完全没有利用矩阵有序的性质。时间和空间都最差,适合作为对拍基准。

class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        arr = [x for row in matrix for x in row]
        arr.sort()
        return arr[k - 1]

二分查找 专题的其他题目

查看 二分查找 全部题目 →