題目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;
}
}