[LeetCode] 378. Kth Smallest Element in a Sorted Matrix

Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix.

Note that it is the kth smallest element in the sorted order, not the kth distinct element.

Example:

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

Constraints:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 300
  • -109 <= matrix[i][j] <= 109
  • All the rows and columns of matrix are guaranteed to be sorted in non-decreasing order.
  • 1 <= k <= n2

Follow up:

  • Could you solve the problem with a constant memory (i.e., O(1) memory complexity)?
  • Could you solve the problem in O(n) time complexity? The solution may be too advanced for an interview but you may find reading this paper fun.

有序矩阵中第K小的元素。

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

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题有三种解法,1是 priority queue(超时),2 是类似23题的归并排序的做法,3是二分法。

首先是 priority queue 的做法。如果是无脑将matrix中所有的元素都加入一个最小堆中然后找第K小的元素,时间复杂度会非常高,因为pq的时间就是O(nlogn) + 遍历input的时间O(n^2),所以时间复杂度起码是O(n^2)起跳。因为matrix里面每一列也是有序的,所以这里优化的方法是将每个元素的坐标和对应的val做成一个tuple加入pq,同时,pq中只有matrix.length个元素进行比较。这里跑一个例子说一下吧。pq一开始加入了1,5,9三个元素,弹出1之后,加入的是10;第二次的比较是介于10,5,9之间的,会弹出5,并且加入跟5同一列的11。以此循环往复K - 1次之后,再下一次poll出来的元素就是第K小的元素了。

时间O(nlogn)

空间O(n)

Java实现

 1 class Solution {
 2     public int kthSmallest(int[][] matrix, int k) {
 3         PriorityQueue<Tuple> pq = new PriorityQueue<>(matrix.length, (a, b) -> (a.val - b.val));
 4         for (int i = 0; i < matrix.length; i++) {
 5             pq.offer(new Tuple(0, i, matrix[0][i]));
 6         }
 7         for (int i = 0; i < k - 1; i++) {
 8             Tuple tuple = pq.poll();
 9             if (tuple.x == matrix.length - 1) {
10                 continue;
11             }
12             pq.offer(new Tuple(tuple.x + 1, tuple.y, matrix[tuple.x + 1][tuple.y]));
13         }
14         return pq.poll().val;
15     }
16 }
17 
18 class Tuple {
19     int x, y, val;
20 
21     public Tuple(int x, int y, int val) {
22         this.x = x;
23         this.y = y;
24         this.val = val;
25     }
26 }

 

[2022年8月更新] 用类似归并排序的做法,跟23题很像。23题是K个有序的链表,这道题是K个有序的数组。如果矩阵是 m x n 的尺寸,那么最小堆里最多只有 m 个元素。

时间O(klogn)

空间O(m)

Java实现

 1 class Solution {
 2     public int kthSmallest(int[][] matrix, int k) {
 3         // [i, j, matrix[i][j]]
 4         PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> a[0] - b[0]);
 5         // 先只加入每一行的第一个元素
 6         for (int i = 0; i < matrix.length; i++) {
 7             queue.offer(new int[] { matrix[i][0], i, 0 });
 8         }
 9 
10         while (!queue.isEmpty() && k > 0) {
11             k--;
12             int[] cur = queue.poll();
13             if (k == 0) {
14                 return cur[0];
15             }
16             if (cur[2] == matrix[0].length - 1) {
17                 continue;
18             }
19             queue.offer(new int[] { matrix[cur[1]][cur[2] + 1], cur[1], cur[2] + 1 });
20         }
21         return -1;
22     }
23 }

 

二分法的思路有一些特别。一开始找的 left 和 right 是 matrix 里面的最小值和最大值(起码这样确定了值的范围),但是在计算 mid 的时候,mid 的值很有可能不在 matrix 中,因为他不是根据坐标得来的,但是一定处于 left 和 right 之间。所以之后 count() 函数是在计算 matrix 中有多少个元素小于/大于 mid,以此决定到底是 left = mid 还是 right = mid。count() 函数还是比较巧妙的,首先记住 matrix 里面每一行和每一列都是有序的。count() 函数是从 matrix 的左下角那个元素开始看,如果左下角那个元素大于 target(nums[mid]),那么说明左下角元素所在的那一行都不满足,所以要 i--;反之如果左下角那个元素小于 target(nums[mid]),那说明左下角那个元素所在的整列元素都小于 target。如果实在记不住,暴力 O(n^2) 遍历数组去数有多少个数字小于 mid 也行。

跑一下例子吧,首先找到左下角元素12,此时 target 是11,12 > 11,此时只能 i--,因为12所在的那一行一定都不满足(12,13,15);再来会在第二行看,10 < 11,res += i + 1 => res += 0 + 1 => res += 1。i是10的横坐标0,又因为10上方的所有元素都一定小于10,所以比11小的元素个数是被累加到 res 上去的。

注意:这里的left mid right是数值,不是索引位置

时间O(nlogn),worse case O(n^2)

空间O(1)

Java实现

 1 class Solution {
 2     public int kthSmallest(int[][] matrix, int k) {
 3         int n = matrix.length;
 4         int left = matrix[0][0];
 5         int right = matrix[n - 1][n - 1];
 6         while (left + 1 < right) {
 7             int mid = left + (right - left) / 2;
 8             int num = count(matrix, mid);
 9             if (num >= k) {
10                 right = mid;
11             } else {
12                 left = mid;
13             }
14         }
15         if (count(matrix, right) <= k - 1) {
16             return right;
17         }
18         return left;
19     }
20 
21     private int count(int[][] matrix, int target) {
22         int n = matrix.length;
23         int res = 0;
24         // start from bottom left corner
25         int i = n - 1, j = 0;
26         while (i >= 0 && j < n) {
27             if (matrix[i][j] < target) {
28                 res += i + 1;
29                 j++;
30             } else {
31                 i--;
32             }
33         }
34         return res;
35     }
36 }

 

JavaScript实现

 1 /**
 2  * @param {number[][]} matrix
 3  * @param {number} k
 4  * @return {number}
 5  */
 6 var kthSmallest = function (matrix, k) {
 7     let lo = matrix[0][0];
 8     let hi = matrix[matrix.length - 1][matrix[0].length - 1] + 1;
 9     while (lo < hi) {
10         let mid = lo + Math.floor((hi - lo) / 2);
11         let count = 0;
12         for (let i = 0; i < matrix.length; i++) {
13             for (let j = 0; j < matrix.length; j++) {
14                 if (matrix[i][j] <= mid) {
15                     count++;
16                 } else {
17                     break;
18                 }
19             }
20         }
21         if (count < k) {
22             lo = mid + 1;
23         } else {
24             hi = mid;
25         }
26     }
27     return lo;
28 };

 

相关题目

373. Find K Pairs with Smallest Sums

378. Kth Smallest Element in a Sorted Matrix

719. Find K-th Smallest Pair Distance

LeetCode 题目总结

posted @ 2020-04-19 06:34  CNoodle  阅读(201)  评论(0编辑  收藏  举报