侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 470 篇文章
  • 累计创建 108 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【排序, 优先队列, (二分查找)】有序矩阵中第 K 小的元素

GabrielxD
2022-05-26 / 0 评论 / 0 点赞 / 37 阅读 / 1,007 字 / 正在检测是否收录...

题目

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


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

你必须找到一个内存复杂度优于 O(n2) 的解决方案。

示例 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
  • -10^9 <= matrix[i][j] <= 10^9
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n^2

进阶:

  • 你能否用一个恒定的内存(即 O(1) 内存复杂度)来解决这个问题?
  • 你能在 O(n) 的时间复杂度下解决这个问题吗?这个方法对于面试来说可能太超前了,但是你会发现阅读这篇文章( this paper )很有趣。

解题

方法一:二维数组扁平化 排序

思路

把矩阵扁平化为一维数组然后直接排序取值。

代码

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int len = matrix.length;
        int[] nums = new int[len * len];
        int i = 0;
        for (int[] arr : matrix) {
            for (int num : arr) {
                nums[i++] = num;
            }
        }
        Arrays.sort(nums);
        return nums[k - 1];
    }
}

方法二:优先队列 归并思想

思路

使用类似 23. 合并K个升序链表 ([【遍历, 优先队列】合并K个升序链表](【遍历, 优先队列】合并K个升序链表.md)) 的归并思想解题,具体实现:

维护一个优先队列(priQueue),里面存储数组,对于其中的每个数组 arrarr[0] 表示矩阵中某一行的当前遍历到的元素arr[1] 表示矩阵中某一行的索引,arr[2] 表示矩阵中某一行的当前遍历到的元素的索引,也就是说 arr[0]matrix 中的坐标是 (arr[1],arr[2])(arr[1], arr[2]),然后重写这个优先队列的比较器,使其以 arr[0] 的大小排序,这样优先队列中头部元素的一定是矩阵中被遍历到的元素中最小的了。

接下来,循环 k-1 次把 k-1 个小的数出队,同时入队出队元素那一列的下一个元素。完成循环后优先队列中的头部元素就一定是第 k 小的元素了,具体地,在遍历的过程中:

  • 出队当前队头元素(最小的)(min)。如果该元素不是当前行中最后一个元素(min[2] < len-1),那么就取当且列中下一个元素,且把列索引加一(++min[2]),然后重新入队即可。

最后返回队头元素表示的矩阵中的元素。

代码

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int len = matrix.length;
        Queue<int[]> priQueue = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        for (int i = 0; i < len; i++) {
            priQueue.offer(new int[]{matrix[i][0], i, 0});
        }
        for (int i = 0; i < k - 1; i++) {
            int[] min = priQueue.poll();
            if (min[2] < len - 1) {
                // 向后遍历当前元素,列索引自增
                min[0] = matrix[min[1]][++min[2]];
                priQueue.offer(min);
            }
        }
        return priQueue.peek()[0];
    }
}

当然,也可以不维护当前遍历到的元素,只维护其坐标,只需要把比较器条件改一下即可:

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int len = matrix.length;
        Queue<int[]> priQueue = new PriorityQueue<>((a, b) -> 
            matrix[a[0]][a[1]] - matrix[b[0]][b[1]]);
        for (int i = 0; i < len; i++) {
            priQueue.offer(new int[]{i, 0});
        }
        for (int i = 0; i < k - 1; i++) {
            int[] min = priQueue.poll();
            if (min[1] < len - 1) {
                min[1]++;
                priQueue.offer(min);
            }
        }
        int[] pos = priQueue.peek();
        return matrix[pos[0]][pos[1]];
    }
}
0

评论区