一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
题目
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例
输入: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出: true
常规遍历
逐个遍历,这实现起来很简单,但最不高效,直接略过。
var searchMatrix = function(matrix, target) {
for (let x = 0; x < matrix.length; x++) {
for (let y = 0; y < matrix[x].length; y++) {
if (matrix[x][y] === target) {
return true;
}
}
}
return false;
};
尝试调整下遍历顺序
结合矩阵生序的条件,仔细端详矩阵的特点。
设想一下,如果我们第一层遍历从后往前,即从最后一行到第一行开始遍历,当前行的第一个元素作为一个分界点,往上都是比他小的,往右都是比他大的。
首先比较当前行的第一个元素和目标值。
如果比目标值小,只需要再遍历当前行来寻找目标值,没找到的话直接 return false。
否则就循环到上一行,继续比较直至结束。
这样很大程度上缩小了我们遍历的范围,提升了性能。
var searchMatrix = function(matrix, target) {
for (let x = matrix.length - 1; x >= 0; x--) {
if (matrix[x][0] === target) {
return true;
}
if (matrix[x][0] < target) {
for (let y = 1; y < matrix[x].length; y++) {
if (matrix[x][y] === target) {
return true;
}
}
return false;
}
}
return false;
};
利用二分查找继续优化
上面我们通过调整遍历的方向优化了一波性能,代码中,我们首先遍历了第一列的值,找到合适位置再遍历当前行。那么这两次查找我们可以尝试用二分查找来实现,这样性能可以又一次提升。
首先遍历第一列,得到目标行。
使用二分时有一点需要注意,当中间值小于目标值时,有两种情况:
1、目标值在下一行,跳到下一行 2、目标值在当前行,直接返回当前行
得到目标行后,再用二分法在目标行查找查找目标值即可。
var searchMatrix = function(matrix, target) {
const binSearchCol = () => {
let left = 0;
let right = matrix.length - 1;
while(left < right) {
let mid = (left + right) >> 1;
if (matrix[mid][0] === target) {
return mid;
}
if (matrix[mid][0] < target) {
// 目标值在当前行,直接返回当前行
if (matrix[mid][matrix[mid].length - 1] >= target) {
return mid;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
const colIndex = binSearchCol();
let left = 0;
let right = matrix[colIndex].length - 1;
while(left < right) {
let mid = (left + right) >> 1;
if (matrix[colIndex][mid] === target) {
return true;
}
if (matrix[colIndex][mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (matrix[colIndex][left] === target) return true;
return false;
};
结语
从解题思路的一步步优化看来,二分法可以看作是查找类题目的最优解,后面遇到类似题目要第一时间想到二分法。