给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。
示例
代码语言:javascript复制matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,
返回 13。
提示
你可以假设 k 的值永远是有效的,1 ≤ k ≤ n2 。
抛砖引玉
暴力排序
- 首先想到的是先拼接数组
- 后排序直接取第 k-1 位(索引 0,第一小)
/**
* @param {number[][]} matrix
* @param {number} k
* @return {number}
*/
var kthSmallest = function (matrix, k) {
let sorted = []
for (let i = 0; i < matrix.length; i ) {
sorted = [...sorted,...matrix[i]]
}
sorted.sort((a, b) => a - b)
return sorted[k - 1]
}
题目中每行和每列元素均按升序排序这个应该可以作为优化的点
1 | 2 | 3 | 4 |
---|---|---|---|
11 | 12 | 13 | 14 |
21 | 22 | 23 | 24 |
31 | 32 | 33 | 34 |
随便找一个符合规则的matrix,找下规则(row表示行,i表示行索引,column,表示列j表示列索引)
- matrix[0][0]最小,matrix[row-1][column-1],最大
- 那么当指针在matrix[i][j],下一个比他大的数会出现的位置会在matrix[x][y]到matrix[row-1][column-1]
- x范围:i到row-1
- y范围:当x为i时(j到column-1),当x为i (大于i)时,在0到j之间可能也会有下一个比他大的数
想要单次遍历逐个递增的来统计第k小的数,会发现下一个比他大的数的区值范围在一个梯形范围内很难具体定位, 换个思路,既然指定一个数,我可以定位到大于他的范围,那假设我已经知道了第k小的元素是m那么,直接统计小于他的数是不是k-1个就可以验证m的真实性了。
二分法
- matrix[0][0]到matrix[row-1][column-1]中任意取一个数mid做第k小的数,(取中间值,会最快取到想要的值)
- 遍历matrix检查小于mid的数是否等于k
- 大于k,则说明m取大了,那么再从matrix[0][0]到m中取个中间值
- 小于k,则说明m取小了,那么再从m到matrix[row-1][column-1]中取个中间值
- 等于k,理论上是取到看第k个,但是因为取的是中间值,也许这个数并不在matrix,那么只能再进行范围划分知道上下线重合
var kthSmallest = function (matrix, k) {
let n = matrix.length;
let left = matrix[0][0];
let right = matrix[n - 1][n - 1];
while (left < right) {
let mid = left parseInt((right - left) / 2, 10);
if (check(matrix, mid, k, n)) {
right = mid;
} else {
left = mid 1;
}
}
return left;
function check(matrix, mid, k, n) {
let i = n - 1;
let j = 0;
let num = 0;
while (i >= 0 && j < n) {
if (matrix[i][j] <= mid) {
num = i 1;
j ;
} else {
i--;
}
}
return num >= k;
}
}
其他解法
- 一行一行合并
- 之后合并的行循环按顺序插入到上一次合并的数组中
- 利用reduce第一个参数做合并的目标数组,异常逐行合并到其中
代码语言:javascript复制reduce方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
var kthSmallest = function(matrix, k) {
if(matrix.length < 1) return 0
let arr = matrix.reduce((a, b) => merge(a, b))
return arr[k - 1]
};
function merge(left, right){
let llen = left.length
let rlen = right.length
let i = 0
let j = 0
let res = []
// 后入数组先按大小入目标数组
while(i < llen && j < rlen){
if (left[i] < right[j]) {
res.push(left[i ])
} else {
res.push(right[j ])
}
}
// 排序逻辑中未入目标数组的子集依次进入
while(i < llen) res.push(left[i ])
while(j < rlen) res.push(right[j ])
return res
}