【刷题之路Ⅱ】牛客 BM18 二维数组中的查找
一、题目描述
原题连接: BM18 二维数组中的查找
题目描述:
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,
每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
数据范围:矩阵的长宽满足0≤n,m≤500 , 矩阵中的值满足0≤val≤10^9
进阶:空间复杂度O(1) ,时间复杂度O(n+m)
示例1
输入: 7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值: true
说明:存在7,返回true
示例2
输入: 1,[[2]]
返回值: false
示例3
输入: 3,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值: false
说明:不存在3,返回false
二、解题
1、方法1——直接遍历
1.1、思路分析
直接遍历数组中的每一个元素,找到target就返回true,
否则就返回false。
1.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
bool Find1(int target, int** array, int arrayRowLen, int* arrayColLen) {
assert(array && arrayColLen);
// 特殊情况特殊处理
if (target < array[0][0] || target > array[arrayRowLen - 1][*arrayColLen - 1]) {
return false;
}
int i = 0;
int j = 0;
for (i = 0; i < arrayRowLen; i++) {
for (j = 0; j < *arrayColLen; j++) {
if (array[i][j] == target) {
return true;
}
}
}
return false;
}
时间复杂度:O(mn),其中m为矩阵的行数,n为矩阵的列数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。
2、方法2——锁定范围后遍历
2.1、思路分析
通过题目描述,我们知道在同一层左边的元素一定是小于右边的元素的
而竖着层数的增加,每一列上的元素也是递增的,因此我们就可以先找到target所在的层数,然后再遍历这一层,若找到target,则直接返回true。
否则就再判断下一层。
例如下图的例子:
2.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
bool Find2(int target, int** array, int arrayRowLen, int* arrayColLen) {
assert(array && arrayColLen);
// 特殊情况特殊处理
if (target < array[0][0] || target > array[arrayRowLen - 1][*arrayColLen - 1]) {
return false;
}
int i = 0;
int j = 0;
for (i = 0; i < arrayRowLen; i++) {
if (target > array[i][0] && target < array[i][*arrayColLen - 1]) {
for (j = 0; j < *arrayColLen - 1; j++) {
if (target == array[i][j]) {
return true;
}
}
}
else if (target == array[i][0] || target == array[i][*arrayColLen - 1]) {
return true;
}
}
return false;
}
时间复杂度:O(mn),其中m为矩阵的行数,n为矩阵的列数,最坏情况下,我们要把数组中的mn - 1 一个元素都遍历一遍。
空间复杂度:O(1),我们只需要用到常数级的额外空间。
2.3、改进
改进思路:
既然题目已经说明是有序的了,那我们找到target可能所在的层数之后,完全可以用二分法在那层查找。
代码实现:
有了以上思路,那我们再写起代码来也就水到渠成了:
bool Find3(int target, int** array, int arrayRowLen, int* arrayColLen) {
assert(array && arrayColLen);
// 特殊情况特殊处理
if (target < array[0][0] || target > array[arrayRowLen - 1][*arrayColLen - 1]) {
return false;
}
int i = 0;
int j = 0;
for (i = 0; i < arrayRowLen; i++) {
if (target > array[i][0] && target < array[i][*arrayColLen - 1]) {
int left = 0;
int right = *arrayColLen - 1;
int mid = 0;
while (left <= right) {
mid = left + (right - left) / 2;
if (target < array[i][mid]) {
right = mid - 1;
}
else if (target > array[i][mid]) {
left = mid + 1;
}
else {
return true;
}
}
}
else if (target == array[i][0] || target == array[i][*arrayColLen - 1]) {
return true;
}
}
return false;
}
时间复杂度:O(mlog2N),其中m为矩阵的行数,N为矩阵的列数,最坏情况下,我们需要在每一行都使用一次二分查找。
空间复杂度:O(1),我们只需要用到常数级的额外空间。
3、方法3——标记值移动法
3.1、思路分析
根据题目的叙述,我们可以事先标记好一个flag值,然后移动这个标值进行查找,具体方法如下:
假设我们选初始的标记值为最靠边的左上角的元素,
1、如果flag == target,直接返回,
2、如果target > flag,说明target在更大的位置,而flag左边的元素显然都是小于flag的,简介就小于target,说明第0行是无效的,
所flag要向下移动到下一行。
3、如果target < flag,说明target在更小的地方,而flag下边的元素又都是大于flag的,间接大于target,说明flag所在的列是无效的,
所以flag要向前移动一列。
4、重复上述过程,直到找到或者找不到。
图解过程如下:
开始,我们的flag在矩阵的右上角:
但此时flag > target 所以最右列就应该被淘汰,flag向左前进一个数:
此时flag为8,还是大于target,所以我们继续淘汰掉一列:
此时的flag为2,小于target,所以我们接下来应该淘汰的是第1行:
这里的flag还是小于target,所以我们在淘汰一行就能顺利地找到target了。
这样做是我们在每次比较的时候,都可以淘汰掉一列或一行,从而大大提高了查找效率。
3.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
bool Find4(int target, int** array, int arrayRowLen, int* arrayColLen) {
assert(array && arrayColLen);
// 特殊情况特殊处理
if (target < array[0][0] || target > array[arrayRowLen - 1][*arrayColLen - 1]) {
return false;
}
int flag = 0;
int row = 0; // 记录flag所在的行数
int col = *arrayColLen - 1; // 记录flag所在的列数
while (row < arrayRowLen && col >= 0) {
flag = array[row][col];
if (target < flag) {
col--;
}
else if (target > flag) {
row++;
}
else {
return true;
}
}
return false;
}
时间复杂度:O(m + n),其中为矩阵的行数,n为矩阵的列数,最坏情况下,我们要把每一行每一列都淘汰掉。
空间复杂度:O(1),我们只需要用到常数级的额外空间。