378.kth-smallest-element-in-a-sorted-matrix.md 6.8 KB
Newer Older
L
lucifer 已提交
1
## 题目地址(378. 有序矩阵中第 K 小的元素)
L
luzhipeng 已提交
2

Y
yulecc 已提交
3
https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/
L
luzhipeng 已提交
4 5 6 7

## 题目描述

```
Y
yulecc 已提交
8 9
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。
L
luzhipeng 已提交
10

Y
yulecc 已提交
11
 
L
luzhipeng 已提交
12

Y
yulecc 已提交
13
示例:
L
luzhipeng 已提交
14 15 16 17 18 19 20 21

matrix = [
   [ 1,  5,  9],
   [10, 11, 13],
   [12, 13, 15]
],
k = 8,

Y
yulecc 已提交
22 23 24 25 26 27
返回 13。
 

提示:
你可以假设 k 的值永远是有效的,1 ≤ k ≤ n2 。

L
luzhipeng 已提交
28 29
```

L
lspeer 已提交
30 31 32 33
## 前置知识

- 二分查找
-
34 35 36 37 38 39

## 公司

- 阿里
- 腾讯
- 字节
L
lucifer 已提交
40

L
luzhipeng 已提交
41 42 43 44 45 46 47 48 49 50 51 52 53
## 思路

显然用大顶堆可以解决,时间复杂度 Klogn n 为总的数字个数,
但是这种做法没有利用题目中 sorted matrix 的特点,因此不是一种好的做法.

一个巧妙的方法是二分法,我们分别从第一个和最后一个向中间进行扫描,并且计算出中间的数值与数组中的进行比较,
可以通过计算中间值在这个数组中排多少位,然后得到比中间值小的或者大的数字有多少个,然后与 k 进行比较,如果比 k 小则说明中间值太小了,则向后移动,否则向前移动。

这个题目的二分确实很难想,我们来一步一步解释。

最普通的二分法是有序数组中查找指定值(或者说满足某个条件的值)。由于是有序的,我们可以根据索引关系来确定大小关系,
因此这种思路比较直接,但是对于这道题目索引大小和数字大小没有直接的关系,因此这种二分思想就行不通了。

L
lucifer 已提交
54
![378.kth-smallest-element-in-a-sorted-matrix-1](https://tva1.sinaimg.cn/large/007S8ZIlly1ghltyc87pwj30gb03u0sx.jpg)
L
luzhipeng 已提交
55 56 57 58 59

(普通的基于索引判断的二分法)

- 我们能够找到矩阵中最大的元素(右下角)和最小的元素(左上角)。我们可以求出值的中间,而不是上面那种普通二分法的索引的中间。

L
lucifer 已提交
60
![378.kth-smallest-element-in-a-sorted-matrix-3](https://tva1.sinaimg.cn/large/007S8ZIlly1ghltyd2629j30ch05faaa.jpg)
L
luzhipeng 已提交
61 62

- 找到中间值之后,我们可以拿这个值去计算有多少元素是小于等于它的。
L
lucifer 已提交
63
  具体方式就是比较行的最后一列,如果中间值比最后一列大,说明中间元素肯定大于这一行的所有元素。 否则我们从后往前遍历直到不大于。
L
luzhipeng 已提交
64

L
lucifer 已提交
65
![378.kth-smallest-element-in-a-sorted-matrix-2](https://tva1.sinaimg.cn/large/007S8ZIlly1ghltyeslbij30by06awep.jpg)
L
luzhipeng 已提交
66

L
lucifer 已提交
67
- 上一步我们会计算一个 count,我们拿这个 count 和 k 进行比较
L
luzhipeng 已提交
68

L
lucifer 已提交
69
- 如果 count 小于 k,说明我们选择的中间值太小了,肯定不符合条件,我们需要调整左区间为 mid + 1
L
luzhipeng 已提交
70

L
lucifer 已提交
71
- 如果 count 大于 k,说明我们选择的中间值正好或者太大了。我们调整右区间 mid
L
luzhipeng 已提交
72

L
lucifer 已提交
73
> 由于 count 大于 k 也可能我们选择的值是正好的, 因此这里不能调整为 mid - 1, 否则可能会得不到结果
L
luzhipeng 已提交
74

L
lucifer 已提交
75
- 最后直接返回 start, end, 或者 mid 都可以,因此三者最终会收敛到矩阵中的一个元素,这个元素也正是我们要找的元素。
L
luzhipeng 已提交
76 77 78

整个计算过程是这样的:

L
lucifer 已提交
79
![378.kth-smallest-element-in-a-sorted-matrix-4](https://tva1.sinaimg.cn/large/007S8ZIlly1ghltyfm2okj30je0fq0uf.jpg)
L
luzhipeng 已提交
80

L
lucifer 已提交
81
这里有一个大家普遍都比较疑惑的点,也是我当初非常疑惑,困扰我很久的点, leetcode 评论区也有很多人来问,就是“能够确保最终我们找到的元素一定在矩阵中么?”
L
luzhipeng 已提交
82

L
lucifer 已提交
83
答案是可以, `相等的时候一定在matrix里面。 因为原问题一定有解,找下界使得start不断的逼近于真实的元素`.
L
luzhipeng 已提交
84 85 86 87 88 89

我是看了评论区一个大神的评论才明白的,以下是[@GabrielaSong](https://leetcode.com/gabrielasong/)的评论原文:

```
The lo we returned is guaranteed to be an element in the matrix is because:
Let us assume element m is the kth smallest number in the matrix, and x is the number of element m in the matrix.
L
lucifer 已提交
90 91
When we are about to reach convergence, if mid=m-1, its count value (the number of elements which are <= mid) would be k-x,
so we would set lo as (m-1)+1=m, in this case the hi will finally reach lo;
L
luzhipeng 已提交
92 93
and if mid=m+1, its count value would be k+x-1, so we would set hi as m+1, in this case the lo will finally reach m.
To sum up, because the number lo found by binary search find is exactly the element which has k number of elements in the matrix that are <= lo,
L
lucifer 已提交
94
 The equal sign guarantees there exists and only exists one number in range satisfying this condition.
L
luzhipeng 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
 So lo must be the only element satisfying this element in the matrix.

```

更多解释,可以参考[leetcode discuss](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173/Share-my-thoughts-and-Clean-Java-Code)

> 如果是普通的二分查找,我们是基于索引去找,因此不会有这个问题。

## 关键点解析

- 二分查找

- 有序矩阵的套路(文章末尾还有一道有序矩阵的题目)

- 堆(优先级队列)

## 代码

```js
/*
 * @lc app=leetcode id=378 lang=javascript
 *
 * [378] Kth Smallest Element in a Sorted Matrix
 */
function notGreaterCount(matrix, target) {
  // 等价于在matrix 中搜索mid,搜索的过程中利用有序的性质记录比mid小的元素个数

  // 我们选择左下角,作为开始元素
  let curRow = 0;
  // 多少列
  const COL_COUNT = matrix[0].length;
  // 最后一列的索引
  const LAST_COL = COL_COUNT - 1;
  let res = 0;

  while (curRow < matrix.length) {
    // 比较最后一列的数据和target的大小
    if (matrix[curRow][LAST_COL] < target) {
      res += COL_COUNT;
    } else {
      let i = COL_COUNT - 1;
      while (i < COL_COUNT && matrix[curRow][i] > target) {
        i--;
      }
      // 注意这里要加1
      res += i + 1;
    }
    curRow++;
  }

  return res;
}
/**
 * @param {number[][]} matrix
 * @param {number} k
 * @return {number}
 */
L
lucifer 已提交
152
var kthSmallest = function (matrix, k) {
L
luzhipeng 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166
  if (matrix.length < 1) return null;
  let start = matrix[0][0];
  let end = matrix[matrix.length - 1][matrix[0].length - 1];
  while (start < end) {
    const mid = start + ((end - start) >> 1);
    const count = notGreaterCount(matrix, mid);
    if (count < k) start = mid + 1;
    else end = mid;
  }
  // 返回start,mid, end 都一样
  return start;
};
```

L
lucifer 已提交
167 168
**复杂度分析**

L
lucifer 已提交
169 170
- 时间复杂度:二分查找进行次数为 $O(log(r-l))$,每次操作时间复杂度为 O(n),因此总的时间复杂度为 $O(nlog(r-l))$。
- 空间复杂度:$O(1)$。
L
lucifer 已提交
171

L
luzhipeng 已提交
172 173 174
## 相关题目

- [240.search-a-2-d-matrix-ii](./240.search-a-2-d-matrix-ii.md)
L
lucifer 已提交
175 176 177 178

大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。
大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)