题目4:二维数组中的查找
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
例子 :
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
查找数字7则返回true,查找数字5则返回false
思路:
看完题目,我们可以先用一个5*5的矩形来表示5*5的数组。
根据题意,有三种情况:
1、若选中的数字a比目标数字x大,则a可能存在的有效区域分为两块(A1-E2)和(A1-B5),如图1所示(深绿色为重叠的有效区域)。
2、若选中的数字a比目标数字x小,则a可能存在的有效区域也分为两块(D1-E5)和(A4-E5),如图2所示。
3、选中的数字a等于目标数字x,则直接返回true。
这么一看,似乎问题挺复杂的:1、下一个数字改怎么选?从哪个位置选?2、重复的区域该怎么避免重复比较?
稍微再分析一下,重复区域都是在数字a的对角线上,那么如果我们为了简化问题,尝试选择一个只有一边对角线的数字的话会怎样呢?(选择位于矩形中四个角位置的数字)
这时会有四个选取点:
选取点A:左上角
图3:若a小于目标数字x,则有效区域为绿色部分,这种情况下若想找出是否有目标数字只能通过遍历数组,这样的话花费的时间会相当多。
图4:若a大于目标数字x,则可以直接返回false
结论:该选取点只适用于目标数字小于数组中最小值的情况,不合格。
选取点B:右上角
当a小于目标数字x的时候,因为a为其所在行中最大的数,故x比该行中所有的数都要大,可以排除a所在行;而当a大于目标数字x的时候,因为a为其所在列中最小的数,故x比改行中所有的数都要小,可以排除a所在列。
有趣的情况出现了,无论a是小于还是大于目标数字x,都可以排除掉当前的行/列,进而缩小有效区域。
这时候,问题就简单了,若每次都取有效区域中右上角的数字进行比较,那么每次都能排除掉一行或一列的数字,最终找到目标数字x或者没有有效区域了也还没有找到目标数字x。
结论:可行!
选取点C:左下角
当选取点在左下角的时候,和选取点在右上角的时候情况有点类似,当选取数字a小于目标数字x的时候,由于a为同一列中最大的数字,故同一列中的数字均小于目标数字x,可排除a的所在列。而当a大于目标数字x时,可排除a所在行。
结论:无论a大于还是小于目标数字x,每次比较都能缩小有效区域,该选取点可行!
选取点D:右下角
当选取点为右下角时,和选取点为左上角类似,该选取点只适用于目标数字x大于数组中所有数字的情况,不可行。
实战演练:
1、使用选取有效区域右上角数字的方法对例子进行查找数字5来验证下结果。
最后一步,由于有效区域的行和列都为0了,故数组中没有数字5,返回false。
2、使用选取有效区域右上角数字的方法对例子进行查找数字7来验证下结果。
关键代码实现:
//RightTopFind:从有效范围的右上角开始查找
//如果当前整数比目标整数小,因为同一行的整数中右边的最大,所以可以排除当前行,继续取有效范围中右上角的整数;
//如果当前整数比目标整数大,因为同一列的整数中最上面的最大,所以可以排除当前列,继续取有效范围中右上角的整数
public static boolean rtFind(int[][] a, int rows, int columns, int number) {
boolean flag = false;
if (a != null && rows > 0 && columns > 0) {
int row = 0;
int column = columns - 1;
while(row < rows && column >= 0 && column < columns) {
if(a[row][column] > number) {
column--;
} else if(a[row][column] < number) {
row++;
} else {
flag = true;
break;
}
}
}
return flag;
}
//LeftBottomFind从有效范围的左下角开始查找
//如果当前整数比目标整数小,因为同一列的整数中最下面的最大,所以可以排除当前列,继续取有效范围中左下角的整数
//如果当前整数比目标整数大,因为同一行的整数中最左边的最小,所以可以排除当前行,继续取有效范围中左小角的整数
public static boolean lbFind(int[][] a, int rows, int columns, int number) {
boolean flag = false;
if (a != null && rows > 0 && columns > 0) {
int row = rows - 1;
int column = 0;
while(column < columns && row >= 0 && row < rows) {
if(a[row][column] > number) {
row--;
} else if(a[row][column] < number) {
column++;
} else {
flag = true;
break;
}
}
}
return flag;
}
测试用例及完整代码:
//面试题4:二维数组中的查找
//在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,
//输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
//例子 : 1 2 8 9
// 2 4 9 12
// 4 7 10 13
// 6 8 11 15
//查找数字7则返回true,查找数字5则返回false
//说说为什么不能取左上角和右下角的整数判断?
public class test4 {
public static void main(String[] args) {
int[][] arr1 = {{1,2,8,9}, {2,4,9,12}, {4,7,10,13}, {6,8,11,15}};
int[][] arr2 = {{1,2,5,8,10}, {2,5,7,10,12}, {4,6,9,13,18}, {8,10,13,15,19}};
System.out.println("rtFind:");
System.out.println(" arr1:");
Test(arr1, 4, 4, 7, "rtFind");//数组中包含目标数字
Test(arr1, 4, 4, 5, "rtFind");//数组中不包含目标数字
Test(arr1, 4, 4, 1, "rtFind");//目标数字为数组中最小值
Test(arr1, 4, 4, 15, "rtFind");//目标数字为数组中最大值
Test(arr1, 4, 4, 0, "rtFind");//目标数字比数组中最小值还小
Test(arr1, 4, 4, 18, "rtFind");//目标数字比数组中最大值还大
System.out.println(" arr2:");
Test(arr2, 4, 5, 8, "rtFind");//数组中包含目标数字
Test(arr2, 4, 5, 3, "rtFind");//数组中不包含目标数字
Test(arr2, 4, 5, 1, "rtFind");//目标数字为数组中最小值
Test(arr2, 4, 5, 19, "rtFind");//目标数字为数组中最大值
Test(arr2, 4, 5, 0, "rtFind");//目标数字比数组中最小值还小
Test(arr2, 4, 5, 23, "rtFind");//目标数字比数组中最大值还大
System.out.println("");
System.out.println("lbFind:");
System.out.println(" arr1:");
Test(arr1, 4, 4, 7, "lbFind");//数组中包含目标数字
Test(arr1, 4, 4, 5, "lbFind");//数组中不包含目标数字
Test(arr1, 4, 4, 1, "lbFind");//目标数字为数组中最小值
Test(arr1, 4, 4, 15, "lbFind");//目标数字为数组中最大值
Test(arr1, 4, 4, 0, "lbFind");//目标数字比数组中最小值还小
Test(arr1, 4, 4, 18, "lbFind");//目标数字比数组中最大值还大
System.out.println(" arr2:");
Test(arr2, 4, 5, 8, "lbFind");//数组中包含目标数字
Test(arr2, 4, 5, 3, "lbFind");//数组中不包含目标数字
Test(arr2, 4, 5, 1, "lbFind");//目标数字为数组中最小值
Test(arr2, 4, 5, 19, "lbFind");//目标数字为数组中最大值
Test(arr2, 4, 5, 0, "lbFind");//目标数字比数组中最小值还小
Test(arr2, 4, 5, 23, "lbFind");//目标数字比数组中最大值还大
}
public static void Test(int[][] a, int rows, int columns, int number, String method){
if (a == null) {
System.out.println("array is null!");
} else {
if (method == "rtFind") {
System.out.println("rtFind:" + number + " " + rtFind(a, rows, columns, number));
} else if (method == "lbFind") {
System.out.println("lbFind:" + number + " " + lbFind(a, rows, columns, number));
} else {
System.out.println("error");
}
}
}
//RightTopFind:从有效范围的右上角开始查找
//如果当前整数比目标整数小,因为同一行的整数中右边的最大,所以可以排除当前行,继续取有效范围中右上角的整数;
//如果当前整数比目标整数大,因为同一列的整数中最上面的最大,所以可以排除当前列,继续取有效范围中右上角的整数
public static boolean rtFind(int[][] a, int rows, int columns, int number) {
boolean flag = false;
if (a != null && rows > 0 && columns > 0) {
int row = 0;
int column = columns - 1;
while(row < rows && column >= 0 && column < columns) {
if(a[row][column] > number) {
column--;
} else if(a[row][column] < number) {
row++;
} else {
flag = true;
break;
}
}
}
return flag;
}
//LeftBottomFind从有效范围的左下角开始查找
//如果当前整数比目标整数小,因为同一列的整数中最下面的最大,所以可以排除当前列,继续取有效范围中左下角的整数
//如果当前整数比目标整数大,因为同一行的整数中最左边的最小,所以可以排除当前行,继续取有效范围中左小角的整数
public static boolean lbFind(int[][] a, int rows, int columns, int number) {
boolean flag = false;
if (a != null && rows > 0 && columns > 0) {
int row = rows - 1;
int column = 0;
while(column < columns && row >= 0 && row < rows) {
if(a[row][column] > number) {
row--;
} else if(a[row][column] < number) {
column++;
} else {
flag = true;
break;
}
}
}
return flag;
}
}