难度:中等 2020/7/2每日一题打卡√ 题目描述 解题思路
简单直接无脑
/* * 378. 有序矩阵中第K小的元素 * 2020/7/2 类似于合并k个有序列表 */ public int kthSmallest(int[][] matrix, int k) { if(k == 1) return matrix[0][0]; int n = matrix.length; PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); for (int i = 0; i < n; i++) { for (int j = 0; j < matrix.length; j++) { if(maxHeap.size() < k) { maxHeap.offer(matrix[i][j]); }else if(matrix[i][j] < maxHeap.peek()) { maxHeap.poll(); maxHeap.offer(matrix[i][j]); } } } return maxHeap.peek(); }题目里没有明显的二分条件,就自己创造二分条件。对于这道题,数组里元素的范围其实是给出来的,左上角元素最小,右下角元素最大,在这个范围内即性二分查找。查找的条件是对于mid,计算数组里元素小于mid的个数,如果个数小于k,说明第k小的元素出现在右半区间,收缩左端点。 如果大于mid的个数,说明mid范围大了,收缩左端点。 问题是最后怎么确定得到的left一定出现在数组里。 自己举个例子就能知道,比如说第k小的数字是15,当mid = 15的时候才刚好满足条件,left此时也等于15
//二分法 public int kthSmallest(int[][] matrix, int k) { int n = matrix.length; int left = matrix[0][0]; int right = matrix[n-1][n-1]; while(left < right) { int mid = left + (right-left)/2; if(checkLessThanK(matrix, mid) < k) { //如果比 mid小的数字小于k个 left = mid + 1; }else { right = mid; } } return left; } //检查到mid为止小于k的数字个数 public int checkLessThanK(int[][] matrix,int mid) { int count = 0,n = matrix.length; for (int i = 0; i < n; i++) { if(matrix[i][n-1] <= mid) { count += n; }else { for (int j = 0; j < n; j++) { if(matrix[i][j] <= mid) { count++; } } } } return count; }这个矩阵的每一行均为一个有序数组。问题即转化为从这 n 个有序数组中找第 k 大的数,可以想到利用归并排序的做法,归并到第 k 个数即可停止。
一般归并排序是两个数组归并,而本题是 n 个数组归并,所以需要用小根堆维护,以优化时间复杂度。
/* * 378. 有序矩阵中第K小的元素 * 2020/7/2 类似于合并k个有序列表 */ public int kthSmallest(int[][] matrix, int k) { if(k == 1) return matrix[0][0]; int n = matrix.length; PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]); for (int i = 0; i < n; i++) { //先把第一列所有元素加入到优先队列里,保存行列信息 pq.offer(new int[] {matrix[i][0],i,0}); } for (int i = 0; i < k-1; i++) { //归并到第k个元素时停止 int[] temp = pq.poll(); if(temp[2]+1 < n) { //把最小值后面的那个元素入队 int p = temp[1],q = temp[2]+1; pq.offer(new int[] {matrix[p][q],p,q}); } } return pq.peek()[0]; }