劍指 offer 第一題: 二維數組中的查找


打算寫 圖解劍指 offer 66 題 的系列文章,不知道大家有沒有興趣 😶

題目描述

在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

題目分析

圖 1圖 1

如果沒有頭緒的話,很顯然使用 暴力解法 是完全可以解決該問題的。

即遍歷二維數組中的每一個元素,時間復雜度:O(n^2)。

其實到這里我們就可以發現,使用這種暴力解法並沒有充分利用題目給出的信息。這個二維數組是有特點的。

  • 每一行都是遞增
  • 每一列都是遞增

圖 2圖 2

解法

解法一:二分法

對於有序數組的查找問題而言,二分法是最容易想到的一個解法。

在這里,對每一行使用二分查找,時間復雜度為 O(nlogn) 。二分查找復雜度 O(logn),一共 n 行,所以是總體的時間復雜度是 O(nlogn) 。

解法二:規律法

根據二維數組由上到下,由左到右遞增的規律。

從左下角開始遍歷,如果當前值比 target 小則往右找,如果比 target 大則往上找,如果存在,必然可以找到目標數字。

即選取右上角或者左下角的元素 a[row] [col] 與 target 進行比較, 當 target 小於元素 a[row] [col] 時,那么 target 必定在元素 a 所在行的左邊,讓 col-- ;當 target 大於元素 a[row] [col] 時,那么 target 必定在元素 a 所在列的下邊,讓 row++ ;

圖 3圖 3

代碼如下:

public class Solution {
     public boolean Find(int target, int [][] array) {
        int row = 0;
        int col = array[0].length - 1;
        while(row <= array.length - 1 && col >= 0){
            if(target == array[row][col])
                return true;
            else if(target > array[row][col])
                row++ ;
            else
                col-- ;
        }
        return false;
    }
}

解法三:二分規律法

將解法一和解法二進行結合:對每行每列都使用二分查找,此時的時間復雜度為 O(logn * logm)

圖 4圖 4

比如查找數字 9,首先使用用二分查找選出一行,總共有 5 行,那么( 0 + 5 ) / 2 = 2,所以我們找出了第 2 行為基准行。

圖 5圖 5

接下來對這一行(即第 2 行)又使用二分查找, 找出這一行(即第 2 行)中最后一個比目標值小的值,這里是 6。

圖 6圖 6

6 及其所在的行和列把這個矩形划分為 4 部分:

圖 7圖 7

  1. 左上部分(圖 7 灰色部分),包括所在行的左邊部分和所在列的上邊部分:這一部分是絕對不會有目標數字的。因為這部分數字肯定比 6 小,而 6 又是小於目標數字的,所以左上部分全部小於目標數字。也就是說這個區域的數字不需要再進行判斷了。
  2. 右下部分(圖 7 綠色部分),包括所在行的右邊部分,但不包括所在列的下面部分, 這一部分也是絕對不會有目標數字的。因為這部分都比 6 右邊的數字 11 大,而 11 又比目標數字 9 更大,所以右下部分全部都比目標數字大。也就是說這個區域的數字也不需要再進行判斷了。
  3. 左下部分(圖 7 藍色部分),可能含有目標數字。
  4. 右上部分(圖 7 棕色部分),可能含有目標數字。

這樣,實際上篩選的區域就只剩下左下部分(圖 7 藍色部分)右上部分(圖 7 棕色部分)這兩塊區域了,相比於解法二而言,使用這種解法平均情況下每一次查找,都可以把行和列的長度減少一半

代碼如下:

public class Solution {
       public boolean Find(int target, int [][] array) {
       // 特殊情況處理
       if (array == null || array.length == 0 || array[0].length == 0) {
            return false;
        }

        int h = array.length - 1;
        int w = array[0].length - 1;

        // 如果目標值小於最小值 或者 目標值大於最大值,那肯定不存在
        if (array[0][0] > target || array[h][w] < target) {
            return false;
        }
        return binarySearchIn2DArray(array, target, 0, h, 0, w);
    }


     public static boolean binarySearchIn2DArray(int[][] array, int target, int startX, int endX, int startY, int endY) {
        if (startX > endX || startY > endY) {
            return false;
        }
        //首先,根據二分法找出中間行
        int x = (startX + endX) / 2;
        //對該行進行二分查找
        int result = binarySearch(array[x], target, startY, endY);
        //找到的值位於 x 行,result 列
        if (array[x][result] == target) {
            return true; // 如果找到則成功
        }
        //對剩余的兩部分分別進行遞歸查找
        return binarySearchIn2DArray(array, target, startX, x - 1, result + 1, endY)
                || binarySearchIn2DArray(array, target, x + 1, endX, startY, result);
    }

     public static int binarySearch(int[] array, int target, int start, int end) {
        int i = (start + end) / 2;
        if (array[i] == target || start > end) { 
            return i;
        } else if (array[i] > target) {
            return binarySearch(array, target, start, i - 1);
        } else {
            return binarySearch(array, target, i + 1, end);
        }
    }
}

感興趣的話可以看看我之前寫的一個項目 LeetCodeAnimation,目前有 13000 star。
原文首發於公號「五分鍾學算法」鏈接:劍指 offer 第一題: 二維數組中的查找


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM