378.有序矩阵中第K小的元素
给你一个 n x n
矩阵 matrix
,其中每行和每列元素均按升序排序,找到矩阵中第 k
小的元素。
请注意,它是 排序后 的第 k
小元素,而不是第 k
个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2)
的解决方案。
力扣解法一:直接排序
最直接的做法是将这个二维数组转成一维数组,并对该一维数组进行排序。最后这个一维数组中的第 kkk 个数即为答案。
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
vector<int> rec;
for (auto& row : matrix) {
for (int it : row) {
rec.push_back(it);
}
}
sort(rec.begin(), rec.end());
return rec[k - 1];
}
};
力扣解法二:归并排序
由题目给出的性质可知,这个矩阵的每一行均为一个有序数组。问题即转化为从这 n 个有序数组中找第 k 大的数,可以想到利用归并排序的做法,归并到第 k 个数即可停止。一般归并排序是两个数组归并,而本题是 n 个数组归并,所以需要用小根堆维护,以优化时间复杂度。
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
struct point {
int val, x, y;
point(int val, int x, int y) : val(val), x(x), y(y) {}
bool operator> (const point& a) const { return this->val > a.val; }
};
priority_queue<point, vector<point>, greater<point>> que;
int n = matrix.size();
for (int i = 0; i < n; i++) {
que.emplace(matrix[i][0], i, 0);
}
for (int i = 0; i < k - 1; i++) {
point now = que.top();
que.pop();
if (now.y != n - 1) {
que.emplace(matrix[now.x][now.y + 1], now.x, now.y + 1);
}
}
return que.top().val;
}
};
力扣解法三:二分查找
我们发现这样的走法时间复杂度为 O(n),即我们可以线性计算对于任意一个 mid,矩阵中有多少数不大于它。这满足了二分查找的性质。不妨假设答案为x,那么可以知道 l≤x≤r,这样就确定了二分查找的上下界。每次对于「猜测」的答案 mid,计算矩阵中有多少数不大于 mid :
如果数量不少于 k,那么说明最终答案 x 不大于 mid;
如果数量少于k,那么说明最终答案 x 大于 mid。
这样我们就可以计算出最终的结果 x 了。
class Solution {
public:
bool check(vector<vector<int>>& matrix, int mid, int k, int n){
int i = n - 1;
int j = 0;
int num = 0;
while(i >= 0 && j < n){
if(matrix[i][j] <= mid){
num += i + 1;
j++;
}else{
i--;
}
}
return num >= k;
}
int kthSmallest(vector<vector<int>>& matrix, int k) {
int n = matrix.size();
int left = matrix[0][0];
int right = matrix[n - 1][n - 1];
while(left <= right){
int mid = left + ((right - left) / 2);
if(check(matrix, mid, k, n)){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
};
收获总结:
上述三种解法,第一种没有利用矩阵的性质,所以时间复杂度最差;第二种解法只利用了一部分性质(每一行是一个有序数列,而忽视了列之间的关系);第三种解法则利用了全部性质,所以时间复杂度最佳。这也启示我们要认真把握题目中的条件与性质,更有利于我们解题。
668.乘法表中第k小的数
几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第 k
小的数字吗?
乘法表是大小为 m x n
的一个整数矩阵,其中 mat[i][j] == i * j
(下标从 1 开始)。
给你三个整数 m
、n
和 k
,请你在大小为 m x n
的乘法表中,找出并返回第 k
小的数字。
算法思路:
class Solution {
public:
int findKthNumber(int m, int n, int k) {
int left = 1, right = m * n;
while(left <= right){
int x = left + (right - left) / 2;
int count = x / n * n;
for(int i = x / n + 1; i <= m; ++i){
count += x / i;
}
if(count >= k){
right = x - 1;
}else{
left = x + 1;
}
}
return left;
}
};
收获总结:
涉及元素极多做不到遍历的二维矩阵里的第K小都可以用二分猜答案的套路,转化为“给定一个数,求矩阵中有多少个数比这个数小”,进而实现二分查找,主站378,719,786,2040题也是类似的思路(其中2040题实现细节极多,代码冗长,导致了rating很高),第一次见到这类题想不出做法很正常,但这个经典套路还是必须掌握~