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


文章目录

摘要

LeetCode 378「有序矩阵中第 K 小的元素」是一道非常容易被写"重"的题

很多人第一反应是:

既然矩阵是有序的,那我全部摊平成数组,再排序不就好了?

没错,能过,但空间复杂度直接炸到 O(n²),而题目明确告诉你:

你必须用更省内存的方式。

这道题真正考的不是排序,而是你有没有意识到:
"答案本身也是有序的,可以用二分去猜。"

这篇文章会重点讲清楚:

  • 为什么这是一个"对值域做二分"的问题
  • 为什么不需要真的把第 k 个元素找出来
  • Swift 下如何写出一个稳定、易读的实现

描述

题目给你一个 n x n 的矩阵 matrix,并保证:

  • 每一行是升序
  • 每一列也是升序

注意这里有一个很重要的点:

它不是整体排序后的第 k 个"不同元素",

而是 排序后的位置第 k 个元素

比如:

text 复制代码
[1,5,9,
 10,11,13,
 12,13,15]

展开后是:

text 复制代码
[1,5,9,10,11,12,13,13,15]

第 8 个元素是 13,而不是跳过重复。

题解答案

这道题最主流、也最稳妥的解法是:

对值域做二分查找 + 利用矩阵有序性统计 ≤ mid 的元素个数

核心思路一句话总结:

  • 矩阵是有序的
  • 第 k 小的值,一定在 [最小值, 最大值] 这个区间里
  • 我们不关心它在哪,只关心 "小于等于某个值的数有多少个"

题解代码分析

为什么这是"二分答案"的题

很多人卡在一个误区里:

二分不是只能用在数组索引上吗?

其实不是。
只要答案满足单调性,就能二分。

在这道题中:

  • 值越小

    • ≤ 这个值的元素数量越少
  • 值越大

    • ≤ 这个值的元素数量越多

这是一个天然的单调关系。

统计 ≤ mid 的元素个数

这是整道题最关键的地方。

因为矩阵行列都有序,我们可以这样做:

  • 左下角 开始

  • 如果当前值 ≤ mid

    • 说明这一列上面的都 ≤ mid
    • 一次性加一整列
  • 如果当前值 > mid

    • 往上移动

这个过程是 O(n) 的。

Swift 可运行 Demo 代码

swift 复制代码
class Solution {

    func kthSmallest(_ matrix: [[Int]], _ k: Int) -> Int {
        let n = matrix.count
        var left = matrix[0][0]
        var right = matrix[n - 1][n - 1]

        while left < right {
            let mid = left + (right - left) / 2
            let count = countLessOrEqual(matrix, mid)

            if count < k {
                left = mid + 1
            } else {
                right = mid
            }
        }

        return left
    }

    private func countLessOrEqual(_ matrix: [[Int]], _ target: Int) -> Int {
        let n = matrix.count
        var row = n - 1
        var col = 0
        var count = 0

        while row >= 0 && col < n {
            if matrix[row][col] <= target {
                count += row + 1
                col += 1
            } else {
                row -= 1
            }
        }

        return count
    }
}

代码拆解说明

1. 为什么 leftright 这样初始化
swift 复制代码
var left = matrix[0][0]
var right = matrix[n - 1][n - 1]

因为:

  • 矩阵整体是有序的
  • 左上角是最小值
  • 右下角是最大值

这是值域的天然边界

2. count < k 为什么要 left = mid + 1

这一步非常关键。

含义是:

  • 当前 mid 太小
  • 小于等于 mid 的元素还不够 k
  • 第 k 小的数一定在右边
3. 为什么最后直接返回 left

因为循环结束条件是:

swift 复制代码
left == right

而这个值,刚好是:

第一个满足"≤ 它的元素个数 ≥ k"的数

也就是第 k 小的元素。

示例测试及结果

swift 复制代码
let solution = Solution()

print(solution.kthSmallest([[1,5,9],[10,11,13],[12,13,15]], 8))
// 13

print(solution.kthSmallest([[-5]], 1))
// -5

输出结果:

text 复制代码
13
-5

时间复杂度

  • 二分次数:log(maxValue - minValue)
  • 每次统计:O(n)

整体复杂度:

text 复制代码
O(n * log(range))

n <= 300 的限制下,非常安全。

空间复杂度

除了几个变量,没有额外数据结构:

text 复制代码
O(1)

完全满足题目的进阶要求。

总结

LeetCode 378 是一道非常适合用来训练"二分答案思维"的题

  • 不在数组上二分
  • 而是在"可能的结果范围"上二分

这种思路在实际开发中也很常见,比如:

  • 资源分配的最小可行值
  • 系统阈值调优
  • 延迟、吞吐量的临界点搜索

如果你能把这道题讲清楚,

那你对"二分"的理解,已经明显高于只会套模板的人了。

相关推荐
Physicist in Geophy.2 小时前
函数,矩阵 算子 泛函
矩阵
ValhallaCoder2 小时前
Day49-图论
数据结构·python·算法·图论
宵时待雨2 小时前
数据结构(初阶)笔记归纳5:单链表的应用
c语言·开发语言·数据结构·笔记·算法
sonadorje2 小时前
矩阵的“秩”是什么?
算法·机器学习·矩阵
wuqingshun3141592 小时前
蓝桥杯 云神的子数组和
算法·蓝桥杯·图论
kklovecode2 小时前
数据结构---顺序表
c语言·开发语言·数据结构·c++·算法
sin_hielo2 小时前
leetcode 1292(二维前缀和)
数据结构·算法·leetcode
炽烈小老头2 小时前
【 每天学习一点算法 2026/01/19】位1的个数
学习·算法
Full Stack Developme2 小时前
时间轮算法
算法