暑期力扣第十六天

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 开始)。

给你三个整数 mn 和 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很高),第一次见到这类题想不出做法很正常,但这个经典套路还是必须掌握~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值