题目链接: https://leetcode.com/problems/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.
Note:
You may assume k is always valid, 1 ≤ k ≤ n2.
思路: 这题还是那种半有序数组的延伸, 我原来没仔细看就做了一个标记, 以为我做过的一个简单题, 然后刚才想做了才发现好像有点不对劲, 然后再一看题号原来是个新题. 因为要找到第k个小的数, 而数组的特性是从左到右有序, 从上到下有序. 所以这题的算法也可以理解为从大到小遍历数组, 所以很符合优先队列的特性. 这样就可以先把最大的数放到优先队列中去, 然后比最大的数小的数要么在他的左边, 要么在他的上面, 再依次将这两个入队列即可. 考虑到优先队列的特性, 总是最大的数在顶部, 所以我们每次就可以将最大的数出队列, 这样直到找到我们要的数.
另外还有一些小技巧, 优先队列要保存哪些信息? 很显然要利用优先队列的性质, 我们要以元素的值为key, 然后还需要的一个信息是这个元素的位置. 也就是这是三个信息, 不太好保存, 如果是两个的话就可以以一个pair的形式保存到优先队列中去了. 而事实上一个元素的坐标是可以保存成一个数的, 比如(x, y)可以保存成x*n+y, 也就是将二维信息转化为一维, 这是一个非常有用的技巧, 我经常会用到. 还有就是访问过的位置不能再入队列了, 所以我们记录一下访问过的位置. 这个可以由unordered_set来完成, 这个数据结构类似于hashmap, 当然也可以用hashmap了, 都可以在O(1)存取数据. 这样基本的算法就完成了. 时间复杂度为最坏为O(n*log(n)), n为数组全部元素个数
代码如下:
class Solution {
public:
int kthSmallest(vector>& matrix, int k) {
priority_queue> que;
int n = matrix.size(), len = n*n;
que.push(make_pair(matrix[n-1][n-1], (n-1)*(n+1)));
unordered_set hash;
hash.insert((n-1)*(n+1));
while(!que.empty())
{
auto val = que.top();
int x = val.second/n, y = val.second%n, tem = val.first;
if(k == len) return tem;
que.pop();
len--;
if(x-1 >= 0 && hash.count((x-1)*n+y)==0)
{
hash.insert((x-1)*n+y);
que.push(make_pair(matrix[x-1][y], (x-1)*n+y));
}
if(y-1 >= 0 && hash.count(x*n+y-1)==0)
{
hash.insert(x*n+y-1);
que.push(make_pair(matrix[x][y-1], x*n + y-1));
}
}
return 0;
}
};
还有一种比较简洁的做法,就是使用二分法初始将left取最小值, right取最大值,然后每次扫描整个数组查找小于(left+right)/2的元素个数,如果此个数小于k,则将left值变为mid+1,否则right = mid,这样的时间复杂为log(Max-Min)*n*log(n),相比与上题来说这种算法依赖与数组中的最大值和最小值的差,不过对于大的数据来说其表现还是优于上个算法.
代码如下:
class Solution {
public:
int kthSmallest(vector>& matrix, int k) {
int n = matrix.size(), left=matrix[0][0], right=matrix[n-1][n-1];
while(left < right)
{
long mid = (left+right)/2, num = 0;
for(int i = 0; i < n; i++)
{
auto it = upper_bound(matrix[i].begin(), matrix[i].end(), mid);
num += it - matrix[i].begin();
}
if(num < k) left = mid+1;
else right = mid;
}
return left;
}
};
参考:https://discuss.leetcode.com/topic/52865/my-solution-using-binary-search-in-c