题目
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
求解思想
最近不经意看到了这个问题,思考了一下,想到了一个递归分治的求解办法。本来既然已经知道求解方法了,依我的个性,就不准备编码来实现这个算法的。奈何前几天刚总结了部分查找算法,刚回顾了一下二分查找,而这个题目又是以二分查找为基础的升级版,所以就想实现一下代码,提升一下写二分查找代码的熟练度。本来以为知道算法思想,编码应该不困难,但是毕竟是打代码嘛,给机器运行的,再加上细节上的考虑不周,还是遇到了些许小问题(逃)。
下面描述一下算法思想:首先根据题目描述,矩阵满足每行元素从左到右升序,每列元素从上到下也是升序。所以对于满足条件的矩阵的所有子矩阵,存在这样一个事实,该矩阵的所有子矩阵左上角第一个元素是子矩阵中最小的元素,右下角最后一个元素是子矩阵中最大的元素。
-
利用上述事实,我们先沿矩阵的对角线 [1, 5, 9, 17, 30] 进行二分查找。以查找元素 15 为例,首先定位到 9 和 17 之间,此时将矩阵划为四个部分,如下图所示,分别令为 A, B, C, D。
matrix.png
- 对于 A 矩阵来说,9 是 A 矩阵的最大元素,而 15 比 9 还大,所以 15 不可能出现在 A 矩阵。同理,17 是 D 矩阵的最小元素,而 15 比 17 还小,所以 15 也不可能出现在 D 矩阵。即 15 只可能出现在 B 矩阵或 C 矩阵。
- 以同样的方式,递归查找 B, C 子矩阵。如果 B, C 子矩阵为行向量或列向量,则直接对 B, C 向量进行普通的二分查找,即可回溯查找结果。
编写代码
算法思想很直观,也很好理解。不过编码需要考虑的面面俱到,而这又是很难的,所以我们需要调试迭代。
C语言的参考代码如下(注释也比较详细,相信理解起来的话问题不大):
#include<stdio.h>
/*由于C语言不能将维数不确定的二维数组作为函数参数进行传递,所以我们需要像如下方式定位元素*/
int index(int* a, int c, int i, int j){//c是矩阵列数
return a[i*c + j];
}
bool searchMatrix(int* a, int c,//这两个参数用于定位矩阵元素
int i, int j, int m, int n,//搜索的矩阵块,(i,j)为左上角坐标,(m,n)为行列数
int target//搜索的目标值
){
if(m == 1 && n == 1){//如果查找范围只有一个元素,则比较一次后就可返回查找结果
if(index(a,c,i,j) == target) return true;
else return false;
}
if(m == 1){//查找范围为一个行向量
int low = j,high = j+n-1;
while(low <= high){
int mid = (low + high)/2;
int value = index(a,c,i,mid);
if(value > target) high = mid - 1;
else if(value < target) low = mid + 1;
else return true;
}
return false;
}
if(n == 1){//查找范围为一个列向量
int low = i,high = i+m-1;
while(low <= high){
int mid = (low + high)/2;
int value = index(a,c,mid,j);
if(value > target) high = mid - 1;
else if(value < target) low = mid +1;
else return true;
}
return false;
}
//查找范围为一个矩阵
int e = m < n? m: n;
int li = i, lj = j, ri = i+e-1, rj = j+e-1;//定位对角线的起点和终点
while(li <= ri){
int mi = (li + ri)/2, mj = (lj + rj)/2;
int value = index(a,c,mi,mj);
if(value > target) ri = mi - 1, rj = mj - 1;
else if(value < target) li = mi + 1, lj = mj + 1;
else return true;
}
//此时li=ri+1;lj=rj+1;
//下面就需要把所有的可能情况都考虑到
if(ri < i) return false;
else if(li > i+e-1){
if(m > n) return searchMatrix(a,c,li,j,m-n,n,target);
else if(m < n) return searchMatrix(a,c,i,lj,m,n-m,target);
else return false;
}else return searchMatrix(a,c,li,j,m-(li-i),lj-j,target) || searchMatrix(a,c,i,lj,li-i,n-(lj-j),target);
}
int main(){
int a[][5] ={
{1, 4, 7, 11, 15},
{2, 5, 8, 12, 19},
{3, 6, 9, 16, 22},
{10, 13, 14, 17, 24},
{18, 21, 23, 26, 30}
};
if(searchMatrix((int*)a,5,0,0,5,5,26)) printf("True");
else printf("False");
return 0;
}
网友评论