当前位置:   article > 正文

leetcode:378. 有序矩阵中第 K 小的元素_leetcode 378

leetcode 378

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {

    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

题目解析

分析数据

(1)数据量。从下面可以看出:

  • 这是一个正方形。
  • 最大数据量: 300 ∗ 300 = 6 ∗ 1 0 4 300 * 300 = 6 * 10^4 300300=6104
    • 算法复杂度不能是 O ( N 2 ) O(N^2) O(N2),因为会超过 1 0 8 10^8 108
    • 至少应该是 O ( N ∗ l o g N ) O(N * logN) O(NlogN)
      在这里插入图片描述

(2)数据特性

  • 值域比较大,所以填表法pass
  • 从左到右递增,从上往下递增,
    • 也就是它们具有单调性, 因此,思考:如何利用它呢?可不可以二分或者利用这个特性淘汰某些数据呢?
    • 另外,matrix[0][0]是最小值,matrix[n-1][n-1]是最大值。
  • 从k的取值范围可以看出k可以找到矩阵中的任意一个数

在这里插入图片描述

二分(最优解)

思路

  • 整个矩阵中最小的数是左上角的数(假设为1),最大的数是右下角的数(假设为1000)。
  • 第k小的数(假设k=100)一定在[1, 1000]之间。
  • 怎么找这个k小的数呢?
    • 先找<= 500的数有几个? 如果有200个,那么目标大了,因此
    • 再找<= 250的数有几个?如果有50个,那么目标小了,因此
    • 再找<=375的数有几个?如果刚好有100个,但是数组中并不一定有这个数,那么应该是<=375(>375的数一定会被淘汰)并且离它最近的那个数
  • 因此,每一次二分都应该返回两个信息:
    • <= 某一个值的个数有几个?
    • 最接近它的是谁?

问题

在这里插入图片描述
思路:要求小于等于target的

  • 尽量淘汰比target大的(列)
  • 尽量保留小于等于target的(行)

从这里我们可以发现一个性质,

  • 任取一个数x满足 l < = x < = r l <= x <= r l<=x<=r,那么矩阵中不大于 midx 的数,肯定全部分布在矩阵的左上角。
  • 例如下图,取 x=8:

在这里插入图片描述

问题

在这里插入图片描述

代码

class Solution {
    struct Info{
        int near;
        int num;
        
        Info(int num, int near) : num(num), near(near){
        }
    };
    
    static Info noMoreNum(vector<vector<int>>& matrix, int value){
        int near = INT16_MIN;
        int num = 0;
        int N = matrix.size(), M = matrix[0].size();
        int row = 0, col = M - 1;
        while (row < N && col >= 0){
            if(matrix[row][col] <= value){
                near = std::max(near, matrix[row][col]);
                num += col + 1;
                row++;
            }else{
                col--;
            }
        }
        return { num, near};
    }

public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int N = matrix.size(), M = matrix[0].size();
        int left = matrix[0][0], right = matrix[N - 1][M - 1];
        int ans = 0;
        while (left <= right){
            int mid = left + ((right - left) >> 1);
            // <=mid 有几个 <= mid 在矩阵中真实出现的数,谁最接近mid
            Info info = noMoreNum(matrix, mid);
            if(info.num < k){
                left = mid + 1;
            }else{
                ans = info.near;
                right = mid - 1;
            }
        }
        return ans;
    }
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

复杂度: O((N + M) * log(max - min))

归并排序(使用堆)

思路

在整个矩阵中,每次弹出矩阵中最小的值,第k个被弹出的就是我们想要的数据

那么,每次弹出矩阵中最小的值呢?

当我们看到下面这个有序矩阵时,我们知道左上角的数字是整个矩阵最小的。

在这里插入图片描述
但是弹出它之后,如何能够保证接下来每一次都还能找到全矩阵中最小的值呢?

解决这个问题的关键,在于维护一组“最小值候选人”。

需要保证的是最小值必然从这组候选人中产生,于是每次只要从候选人中弹出一个最小的就可以了。

我们来选择第一组候选人,在这里可以选择第一列,因为每一个数字都是其对应行的最小值,全局最小必然在其中

在这里插入图片描述
第一次弹出很简单,将左上角的1弹出即可。

1弹出之后,我们如何找到下一个候选人呢?

在这里插入图片描述

很简单,刚才弹出的位置右移一格就行了,这样不是还能保证候选人链表中每一个数字都是每一行的最小值吗,那全局最小值必然在其中!

我们每次弹出候选人当中的最小值,然后把上次弹出候选人的右边一个补进来,就能一直保证全局最小值在候选人列表中产生,
在这里插入图片描述

说白了就是每行一路,五路归并,用堆比较最小值。所以上下的顺序是没有用到,只用到左右的顺序

class Solution {
    struct Element {
        int val;
        int x;
        int y;

        Element(int val, int x, int y) : val(val), x(x), y(y) {
        }

        // 方法二定义pq 需要重载 operator>
        bool operator> (const Element &other) const {
            return this->val > other.val;
        }
    };

public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int N = matrix.size(), M = matrix[0].size();

        // 定义pq方法一:用自定义的比较
//        auto comp = [](Element e1, Element e2) { return e1.val > e2.val; };
//        priority_queue<Element, vector<Element>, decltype(comp)> pq (comp);    // 用优先队列表示最小堆

        // 定义pq方法二:在 Element 中重载 operator>,然后用内置的std::greater
         priority_queue<Element, vector<Element>, std::greater<Element>> pq;


        // 初始化:将 matrix 的第一列加入 pq 作为初始的「最小候选值」列表
        for (int r = 0; r < N; ++r) {
            Element e(matrix[r][0], r, 0);
            pq.push(e);
        }

        // 弹出前 k-1 小的值
        for (int i = 0; i < k - 1; ++i) {
            Element top = pq.top(); pq.pop();
            if (top.y != N - 1) {   // 当前 (top.x, top.y) 的右边还有数字,将它右边的数 push 到优先队列中
                Element e(matrix[top.x][top.y + 1], top.x, top.y + 1);
                pq.push(e);
            }
        }

        return pq.top().val;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

类似题目

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/982702
推荐阅读
相关标签
  

闽ICP备14008679号