• Medium | LeetCode 378. 有序矩阵中第 K 小的元素 | 优先队列 | 二分法


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

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

    示例 1:

    输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
    输出:13
    解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
    

    示例 2:

    输入:matrix = [[-5]], k = 1
    输出:-5
    

    提示:

    • n == matrix.length
    • n == matrix[i].length
    • 1 <= n <= 300
    • -109 <= matrix[i][j] <= 109
    • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
    • 1 <= k <= n2

    解题思路

    方法一: 优先队列

    将二维数组看作是N个一维数组, 采用归并的方法, 如题 Hard | LeetCode 23. 合并K个升序链表 | 分治 | 优先队列 将每个数字及其在矩阵当中的坐标存入优先队列当中。通过优先队列出队K次, 即可得到第K个小值。

    public int kthSmallest(int[][] matrix, int k) {
       Queue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o[0]));
       int row = matrix.length;
       int colume = matrix[0].length;
       // 将每行的第一个元素加入优先队列
       for (int i = 0; i < row; i++) {
           queue.offer(new int[]{matrix[i][0], i, 0});
       }
       int[] heapTop = null;
       // 优先队列出队K次, 得到第K小数
       for (int i = 0; i < k; i++) {
           heapTop = queue.poll();
           if (heapTop[2] < colume - 1) {
               // 将出队元素的后面一个元素加入到优先队列当中
               queue.offer(new int[]{matrix[heapTop[1]][heapTop[2] + 1], heapTop[1], heapTop[2] + 1});
           }
       }
       return heapTop[0];
    }
    

    方法二: 二分法

    矩阵最小值是matrix[0][0], 设为l,矩阵最大值为matrix[matrix.length-1][matrix[0].length-1] 设为r, 分别作为矩阵第K小值的上界和下界。

    然后从上界和下界取一个值, 为target。然后从矩阵中找小于等于target值的数量(这里可以把二维矩阵看做N个一维数组, 从这N个一维数组找小于等于target值的数量, 然后求和)。

    如果小于等于target值的数字的数量小于K, 则说明这个target值一定过小了。

    如果小于等于target值的数字的数量大于K, 这个target值可能过大了, 当然也有可能不过大, 因为target值在矩阵当中可能不止一个。

    如果小于等于target值的数字的数量等于K, 这个target值可能刚好合适, 也有可能过大了。

    这样二分的规则就出来了。

    public int kthSmallest(int[][] matrix, int k) {
        int l = matrix[0][0], r = matrix[matrix.length-1][matrix[0].length-1];
        while (l < r) {
            int mid = (l + r) >> 1;
            if (check(matrix, k, mid)) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        return l;
    }
    
    public boolean check(int[][] matrix, int k, int target) {
        int sum = 0;
        // 注意这里要求的是小于等于target值的数量, 也就是找大于target值的下标
        // r的值必须是, matrix[0].length 而不能是其值减1
        int l = 0, r = matrix[0].length; 
        for (int i = 0; i < matrix.length; i++) {
            // 每次找当前行时, 二分左边界还是0, 但是右边界一定不大于上次定位的r
            // 因为同一列, 下一行的值一定大于上一行
            l = 0;        
            while (l < r) {
                int mid = (l + r) >> 1;
                if (matrix[i][mid] <= target) {
                    l = mid + 1;
                } else {
                    r = mid;
                }
            }
            sum += l;
        }
        // 如果矩阵中小于等于target的数量 小于K, 则说明target值过小了
        // 如果矩阵中小于等于target的数量 大于K, 则说明target值可能存在过大了,
        // 但也有可能刚好是这个target值, 因为target值可能有重复
        return sum < k;
    }
    
  • 相关阅读:
    iOS 微信支付SDK与微信友盟分享两者同时集成时,出现的问题与解决之路。
    Object-C语言Block的实现方式
    使用Mac命令别名,提升工作效率
    利用OC对象的消息重定向forwardingTargetForSelector方法构建高扩展性的滤镜功能
    渐变色进度条的两种绘制方案
    设计模式应用场景之Model设计中可以用到的设计模式
    有趣的赫夫曼树
    技术团队管理者的问题视角
    SSH安全登陆原理:密码登陆与公钥登陆
    为什么HashMap继承了AbstractMap还要实现Map?
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14612546.html
Copyright © 2020-2023  润新知