1. 程式人生 > >小樣?別以為你穿了幾個馬甲就不認得你是二分法!

小樣?別以為你穿了幾個馬甲就不認得你是二分法!

>需要更多演算法動圖詳解,可以微信搜尋[袁廚的演算法小屋] 今天給大家帶來的是二分查詢及其變種的總結,大家一定要看到最後呀,用心滿滿,廢話不多說,讓導演幫我們把鏡頭切到袁記菜館吧! 袁記菜館內。。。。 > 店小二:掌櫃的,您進貨回來了呀,喲!今天您買這魚挺大呀! > > 袁廚:那是,這是我今天從咱們江邊買的,之前一直去菜市場買,那裡的老貴了,你猜猜我今天買的多少錢一條。 > > 店小二:之前的魚,30個銅板一條,今天的我猜26個銅板。 > > 袁廚:貴了。 > > 店小二:還貴呀!那我猜20個銅板! > > 袁廚:還是貴了。 > > 店小二:15個銅板。 > > 袁廚:便宜了 > > 店小二:18個銅板 > > 袁廚:恭喜你猜對了 上面的例子就用到了我們的二分查詢思想,如果你玩過類似的遊戲,那二分查詢理解起來肯定很輕鬆啦,下面我們一起征服二分查詢吧! --- 完全有序 ## 二分查詢 > 二分查詢也稱折半查詢(Binary Search),是一種在有序陣列中查詢某一特定元素的搜尋演算法。我們可以從定義可知,運用二分搜尋的前提是陣列必須是有序的,這裡需要注意的是,我們的輸入不一定是陣列,也可以是陣列中某一區間的起始位置和終止位置 通過上面二分查詢的定義,我們知道了二分查詢演算法的作用及要求,那麼該演算法的具體執行過程是怎樣的呢? 下面我們通過一個例子來幫助我們理解。我們需要在 nums 陣列中,查詢元素 8 的索引 ```java int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 ``` > (1)我們需要定義兩個指標分別指向陣列的頭部及尾部,這是我們在整個陣列中查詢的情況,當我們在陣列 > > 某一區間進行查詢時,可以輸入陣列,起始位置,終止位置進行查詢。 ![二分查詢1](https://content.markdowner.net/pub/6jVbeV-Lk0B1W9) > (2)找出mid,該索引為 mid =(left + right)/ 2,但是這樣寫有可能溢位,所以我們需要改進一下寫成 > > mid = left +(right - left)/ 2 或者 left + ((right - left ) >> 1) 兩者作用是一樣的,都是為了找到兩指標的中 > > 間索引,使用位運算的速度更快。那麼此時的 mid = 0 + (8-0) / 2 = 4 ![二分查詢2](https://content.markdowner.net/pub/LbNEM9-dypLWj9) > (3)此時我們的 mid = 4,nums[mid] = 6 < target,那麼我們需要移動我們的 left 指標,讓left = mid + 1,下次則可以在新的 left 和 right 區間內搜尋目標值,下圖為移動前和移動後 ![](https://content.markdowner.net/pub/PbGMAO-Lw9Ya4P) > (4)我們需要在 left 和 right 之間計算 mid 值,mid = 5 + (8 - 5)/ 2 = 6 然後將 nums[mid] 與 target 繼續比較,進而決定下次移動left 指標還是 right 指標,見下圖 ![二分查詢3](https://content.markdowner.net/pub/A4MnVa-LP10oEA) > (5)我們發現 nums[mid] > target,則需要移動我們的 right 指標, 則 right = mid - 1;則移動過後我們的 left 和 right 會重合,這裡是我們的一個重點大家需要注意一下,後面會對此做詳細敘述。 ![二分查詢4](https://content.markdowner.net/pub/GEbgq1-LEmxO9g) > (6)我們需要在 left 和 right 之間繼續計算 mid 值,則 mid = 5 +(5 - 5)/ 2 = 5 ,見下圖,此時我們將 nums[mid] 和 target 比較,則發現兩值相等,返回 mid 即可 ,如果不相等則跳出迴圈,返回 -1。 ![二分查詢6](https://content.markdowner.net/pub/aLpV35-pRR1Myo) 二分查詢的執行過程如下 1.從已經排好序的陣列或區間中,取出中間位置的元素,將其與我們的目標值進行比較,判斷是否相等,如果相等 則返回。 2.如果 nums[mid] 和 target 不相等,則對 nums[mid] 和 target 值進行比較大小,通過比較結果決定是從 mid 的左半部分還是右半部分繼續搜尋。如果 target > nums[mid] 則右半區間繼續進行搜尋,即 left = mid + 1; 若 target < nums[mid] 則在左半區間繼續進行搜尋,即 right = mid -1; **動圖解析** ![二分查詢2](https://content.markdowner.net/pub/eadv4Y-LGaD8mV) 下面我們來看一下二分查詢的程式碼,可以認真思考一下 if 語句的條件,每個都沒有簡寫。 ```java public static int binarySearch(int[] nums,int target,int left, int right) { //這裡需要注意,迴圈條件 while (left <= right) { //這裡需要注意,計算mid int mid = left + ((right - left) >> 1); if (nums[mid] == target) { return mid; }else if (nums[mid] < target) { //這裡需要注意,移動左指標 left = mid + 1; }else if (nums[mid] > target) { //這裡需要注意,移動右指標 right = mid - 1; } } //沒有找到該元素,返回 -1 return -1; } ``` 二分查詢的思路及程式碼已經理解了,那麼我們來看一下實現時容易出錯的地方 1.計算 mid 時 ,不能使用 (left + right )/ 2,否則有可能會導致溢位 2.while (left < = right) { } 注意括號內為 left <= right ,而不是 left < right ,我們繼續回顧剛才的例子,如果我們設定條件為 left < right 則當我們執行到最後一步時,則我們的 left 和 right 重疊時,則會跳出迴圈,返回 -1,區間內不存在該元素,但是不是這樣的,我們的 left 和 right 此時指向的就是我們的目標元素 ,但是此時 left = right 跳出迴圈 3.left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我們思考一下這種情況,見下圖,當我們的target 元素為 16 時,然後我們此時 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果設定 left = mid 的話,則會進入死迴圈,mid 值一直為7 。 ![二分查找出差](https://content.markdowner.net/pub/Xn3yAY-pxjA1xg) 下面我們來看一下二分查詢的遞迴寫法 ```java public static int binarySearch(int[] nums,int target,int left, int right) { if (left <= right) { int mid = left + ((right - left) >> 1); if (nums[mid] == target) { //查詢成功 return mid; }else if (nums[mid] > target) { //新的區間,左半區間 return binarySearch(nums,target,left,mid-1); }else if (nums[mid] < target) { //新的區間,右半區間 return binarySearch(nums,target,mid+1,right); } } //不存在返回-1 return -1; } ``` ### 例題: #### 題目描述 > 給定一個排序陣列和一個目標值,在陣列中找到目標值,並返回其索引。如果目標值不存在於陣列中,返回它將會被按順序插入的位置。 > > 你可以假設陣列中無重複元素。 > 示例 1: > 輸入: [1,3,5,6], 5 > 輸出: 2 示例 2: > 輸入: [1,3,5,6], 2 > 輸出: 1 示例 3: > 輸入: [1,3,5,6], 7 > 輸出: 4 示例 4: > 輸入: [1,3,5,6], 0 > 輸出: 0 #### 題目解析 這個題目完全就和咱們的二分查詢一樣,只不過有了一點改寫,那就是將咱們的返回值改成了 left,具體實現過程見下圖 ![搜尋插入位置](https://content.markdowner.net/pub/meRw8g-E3RGvgj) ``` class Solution { public int searchInsert(int[] nums, int target) { int left = 0, right = nums.length-1; //注意迴圈條件 while (left <= right) { //求mid int mid = left + ((right - left ) >> 1); //查詢成功 if (target == nums[mid]) { return mid; //右區間 } else if (nums[mid] < target) { left = mid + 1; //左區間 } else if (nums[mid] > target) { right = mid - 1; } } //返回插入位置 return left; } } ``` ## 二分查詢變種一 上面我們說了如何使用二分查詢在陣列或區間裡查出特定值的索引位置。但是我們剛才數組裡面都沒有重複值,查到返回即可,那麼我們思考一下下面這種情況 ![](https://content.markdowner.net/pub/B4wWO1-Eg8xPVL) 此時我們數組裡含有多個 5 ,我們查詢是否含有 5 可以很容易查到,但是我們想獲取第一個 5 和 最後一個 5 的位置應該怎麼實現呢?哦!我們可以使用遍歷,當查詢到第一個 5 時,我們設立一個指標進行定位,然後到達最後一個 5 時返回,這樣我們就能求的第一個和最後一個五了?因為我們這個文章的主題就是二分查詢,我們可不可以用二分查詢來實現呢?當然是可以的。 #### 題目描述 > 給定一個按照升序排列的整數陣列 nums,和一個目標值 target。找出給定目標值在陣列中的開始位置和結束位置。 > > 如果陣列中不存在目標值 target,返回 [-1, -1]。 > 示例 1: > 輸入:nums = [5,7,7,8,8,10], target = 8 > 輸出:[3,4] 示例 2: > 輸入:nums = [5,7,7,8,8,10], target = 6 > 輸出:[-1,-1] 示例 3: > 輸入:nums = [], target = 0 > 輸出:[-1,-1] #### 題目解析 這個題目很容易理解,我們在上面說了如何使用遍歷解決該題,但是這個題目的目的就是讓我們使用二分查詢,我們來逐個分析,先找出目標元素的下邊界,那麼我們如何找到目標元素的下邊界呢? 我們來重點分析一下剛才二分查詢中的這段程式碼 ```java if (nums[mid] == target) { return mid; }else if (nums[mid] < target) { //這裡需要注意,移動左指標 left = mid + 1; }else if (nums[mid] > target) { //這裡需要注意,移動右指標 right = mid - 1; } ``` 我們只需在這段程式碼中修改即可,我們再來剖析一下這塊程式碼,nums[mid] == target 時則返回,nums[mid] < target 時則移動左指標,在右區間進行查詢, nums[mid] > target時則移動右指標,在左區間內進行查詢。 那麼我們思考一下,如果此時我們的 nums[mid] = target ,但是我們不能確定 mid 是否為該目標數的左邊界,所以此時我們不可以返回下標。例如下面這種情況。![二分查詢下邊界](https://content.markdowner.net/pub/Q6aEgE-wbg5w6e) 此時 mid = 4 ,nums[mid] = 5,但是此時的 mid 指向的並不是第一個 5,所以我們需要繼續查詢 ,因為我們要找 的是數的下邊界,所以我們需要在 mid 的值的左區間繼續尋找 5 ,那我們應該怎麼做呢?我們只需在 target <= nums[mid] 時,讓 right = mid - 1即可,這樣我們就可以繼續在 mid 的左區間繼續找 5 。是不是聽著有點繞,我們通過下面這組圖進行描述。 ![左邊界1](https://content.markdowner.net/pub/Q6aEgE-VvGYm7q) ![左邊界2](https://content.markdowner.net/pub/ObAD8w-p1N86B7) 其實原理很簡單,就是我們將小於和等於合併在一起處理,當 target <= nums[mid] 時,我們都移動右指標,也就是 right = mid -1,還有一個需要注意的就是,我們計算下邊界時最後的返回值為 left ,當上圖結束迴圈時,left = 3,right = 2,返回 left 剛好時我們的下邊界。我們來看一下求下邊界的具體執行過程。 **動圖解析** ![二分查詢下邊界](https://content.markdowner.net/pub/0R6Qqb-R0nR3W)計算下邊界程式碼 ```java int lowerBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { //這裡需要注意,計算mid int mid = left + ((right - left) >> 1); if (target <= nums[mid]) { //當目標值小於等於nums[mid]時,繼續在左區間檢索,找到第一個數 right = mid - 1; }else if (target > nums[mid]) { //目標值大於nums[mid]時,則在右區間繼續檢索,找到第一個等於目標值的數 left = mid + 1; } } return left; } ``` 計算上邊界時算是和計算上邊界時條件相反, 計算下邊界時,當 target <= nums[mid] 時,right = mid -1;target > nums[mid] 時,left = mid + 1; 計算上邊界時,當 target < nums[mid] 時,right = mid -1; target >= nums[mid] 時 left = mid + 1;剛好和計算下邊界時條件相反,返回right。 **計算上邊界程式碼** ```java int upperBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { //求mid int mid = left + ((right - left) >> 1); //移動左指標情況 if (target >= nums[mid]) { left = mid + 1; //移動右指標情況 }else if (target < nums[mid]) { right = mid - 1; } } return left; } ``` **題目完整程式碼** ``` class Solution { public int[] searchRange (int[] nums, int target) { int upper = upperBound(nums,target); int low = lowerBound(nums,target); //不存在情況 if (upper < low) { return new int[]{-1,-1}; } return new int[]{low,upper}; } //計算下邊界 int lowerBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { //這裡需要注意,計算mid int mid = left + ((right - left) >> 1); if (target <= nums[mid]) { //當目標值小於等於nums[mid]時,繼續在左區間檢索,找到第一個數 right = mid - 1; }else if (target > nums[mid]) { //目標值大於nums[mid]時,則在右區間繼續檢索,找到第一個等於目標值的數 left = mid + 1; } } return left; } //計算上邊界 int upperBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (target >= nums[mid]) { left = mid + 1; }else if (target < nums[mid]) { right = mid - 1; } } return right; } } ``` ## 二分查詢變種二 我們在上面的變種中,描述瞭如何找出目標元素在陣列中的上下邊界,然後我們下面來看一個新的變種,如何從陣列或區間中找出第一個大於或最後一個小於目標元素的數的索引,例 nums = {1,3,5,5,6,6,8,9,11} 我們希望找出第一個大於 5的元素的索引,那我們需要返回 4 ,因為 5 的後面為 6,第一個 6 的索引為 4,如果希望找出最後一個小於 6 的元素,那我們則會返回 3 ,因為 6 的前面為 5 最後一個 5 的索引為 3。好啦題目我們已經瞭解,下面我們先來看一下如何在陣列或區間中找出第一個大於目標元素的數吧。 找出第一個大於目標元素的數,大概有以下幾種情況 ![模糊邊界情況](https://content.markdowner.net/pub/0R6Qqb-aWm6mYR) 1.陣列包含目標元素,找出在他後面的第一個元素 2.目標元素不在陣列中,陣列內的部分元素大於它,此時我們需要返回第一個大於他的元素 3.目標元素不在陣列中,且陣列中的所有元素都大於它,那麼我們此時返回陣列的第一個元素即可 4.目標元素不在陣列中,且陣列中的所有元素都小於它,那麼我們此時沒有查詢到,返回 -1 即可。 既然我們已經分析完所有情況,那麼這個題目對咱們就沒有難度了,下面我們描述一下案例的執行過程 > nums = {1,3,5,5,6,6,8,9,11} target = 7 上面的例子中,我們需要找出第一個大於 7 的數,那麼我們的程式是如何執行的呢? ![二分查詢模糊邊界目標值](https://content.markdowner.net/pub/nXbwdQ-Lw5Gv49) 上面的例子我們已經弄懂了,那麼我們看一下,當 target = 0時,程式應該怎麼執行呢? ![模糊邊界目標0](https://content.markdowner.net/pub/4Vnzkm-LWgBngd) OK!我們到這一步就能把這個變種給整的明明白白的了,下面我們看一哈程式程式碼吧,也是非常簡單的。 ```java public static int lowBoundnum(int[] nums,int target,int left, int right) { while (left <= right) { //求中間值 int mid = left + ((right - left) >> 1); //大於目標值的情況 if (nums[mid] > target) { //返回 mid if (mid == 0 || nums[mid-1] <= target) { return mid; } else{ right = mid -1; } } else if (nums[mid] <= target){ left = mid + 1; } } //所有元素都小於目標元素 return -1; } ``` 通過上面的例子我們應該可以完全理解了那個變種,下面我們繼續來看以下這種情況,那就是如何找到最後一個小於目標數的元素。還是上面那個例子 > nums = {1,3,5,5,6,6,8,9,11} target = 7 查詢最後一個小於目標數的元素,比如我們的目標數為 7 ,此時他前面的數為 6,最後一個 6 的索引為 5,此時我們返回 5 即可,如果目標數元素為 12,那麼我們最後一個元素為 11,仍小於目標數,那麼我們此時返回 8,即可。這個變種其實算是上面變種的相反情況,上面的會了,這個也完全可以搞定了,下面我們看一下程式碼吧。 ```java public static int upperBoundnum(int[] nums,int target,int left, int right) { while (left <= right) { int mid = left + ((right - left) >> 1); //小於目標值 if (nums[mid] < target) { //看看是不是當前區間的最後一位,如果當前小於,後面一位大於,返回當前值即可 if (mid == right || nums[mid+1] >= target) { return mid; } else{ left = mid + 1; } } else if (nums[mid] >= target){ right = mid - 1; } } //沒有查詢到的情況 return -1; } ``` --- 不完全有序 面試前必知必會的二分查詢及其變種(下) 之前給大家總結總結了一波二分查詢的變種,但是因為變種太多一篇放不下,所以我們在這一篇裡繼續進行總結,之前我們說二分查詢需要在完全有序的數組裡使用,那麼不完全有序時可以用嗎? 例: ![](https://content.markdowner.net/pub/V3QR6G-a4k8555) 上面的新陣列雖然不是完全有序,但是也可以看成是由一個完全有序的陣列翻折得到的。或者可以理解成兩個有序陣列,且第二個陣列的最大值小於第一的最小值,我們將其拼接,拼接成了一個不完全有序的陣列,在這個陣列中我們需要找到 target ,找到後返回其索引,如果沒有找到則返回 -1; 我們第一次看到這種題目時,可能會想到,我們只需要挨個遍歷就好啦,發現後返回索引即可,這樣做當然是可以滴,那麼我們可不可以使用二分查詢呢? 下面我們看一下解決該題的具體思路。 首先我們設想一下 mid 值會落到哪裡,我們一起來想一下。 是不是隻有兩種情況,和 left 在一個數組,同時落在 陣列1 或同時在 陣列2,或者不在一個數組, left 在陣列1,mid 在陣列2。想到這裡咱們這個題目已經完成一半了。 ![mid值情況](https://content.markdowner.net/pub/V3QR6G-zppaypw) 那麼我們先來思考一下,?我們可以根據 nums[mid] 和 nums[left] 判斷,是因為我們的 mid 一定是會落在 left 和 right 之間,那如果 nums[mid] >= nums[left] 時,說明他倆落在一個數組裡了,如果 nums[mid] < nums[left] 時,說明他倆落在了不同的陣列,此時left 在陣列1 mid在陣列2. 注:left 和 mid 落在同一陣列時,不能是 left 在 陣列2 ,mid 在 陣列1 呢?因為咱們的 mid 是通過 left 和 right 的下標求得,所以應該在 left 和 right 中間 如果我們的 mid 和 left 在同一個陣列內時?咱們的 target 會有幾種情況呢?我們通過都落在 陣列1 舉例。 ![left左](https://content.markdowner.net/pub/oOD6go-Ld55r9d) 無非也是兩種情況,用我們上面的例子來說, 1.**落在 mid 的左邊**,當前例子中 情況是落在 [4,7)區間內,即 4 <= target < 7 ,也就是 target >= nums[left] && target < nums[mid],此時我們讓 right = mid -1,讓 left 和 right 都落到陣列 1 中,下次查詢我們就是在陣列1中進行了,完全有序, 2.**落在 mid 的右邊**,此時例子中 target 不落在 [4,7)區間內,那就 target = 8 或 0 <= target <= 2 (此時我們的 target 均小於 nums[left]) 兩種情況,也就是target > nums[mid] || target < nums[left] 此時我們讓 left = mid + 1即可,也是為了慢慢將left 和 right 指標趕到一個有序陣列內。 那我們在來思考一下當 mid 值落在 **陣列2** 中時,target 會有幾種情況呢?其實和上面的例子思路一致,情況相反而已。 ![right右](https://content.markdowner.net/pub/WDOyP5-0bM547L) 1. target <= nums[right] && target > nums[mid] > 這裡和上面的對應,此時的情況就是整個落在右半部分,我們下次就可以在陣列2內進行查詢。 2. target > nums[right] || target < nums[mid] > 這裡就是和上面的第二種情況對應,落在 mid 的左半部分,我們儘量將兩個指標趕到一起 希望我的表達能夠讓大家對這個變種理解透徹,如果沒能讓各位理解,或者有表達不當的地方歡迎各位批評指導。然後我們一起來做一下 leetcode 33 題吧。 ### 搜尋旋轉排序陣列 #### 題目描述 給你一個整數陣列 nums ,和一個整數 target 。 該整數陣列原本是按升序排列,但輸入時在預先未知的某個點上進行了旋轉。(例如,陣列 [0,1,2,4,5,6,7] 可能變為 [4,5,6,7,0,1,2] )。 請你在陣列中搜索 target ,如果陣列中存在這個目標值,則返回它的索引,否則返回 -1 。 示例 1: > 輸入:nums = [4,5,6,7,0,1,2], target = 0 > 輸出:4 示例 2: > 輸入:nums = [4,5,6,7,0,1,2], target = 3 > 輸出:-1 示例 3: > 輸入:nums = [1], target = 0 > 輸出:-1 #### 題目解析 這個題目的解答方法,咱們在上面已經有所描述,下面我們來看一下下面這個例子的程式碼執行過程吧. > 輸入 nums = [4,5,6,7,8,0,1,2] target = 8 下面我們看題目程式碼吧,如果還沒有完全理解的同學,可以仔細閱讀 if ,else if 裡面的語句,還有註釋,一定可以整透的。 #### 題目程式碼 ```java class Solution { public int search(int[] nums, int target) { //左右指標 int left = 0; int right = nums.length - 1; while (left <= right) { int mid = left+((right-left)>>1); if (nums[mid] == target) { return mid; } //落在同一陣列的情況,同時落在陣列1 或 陣列2 if (nums[mid] >= nums[left]) { //target 落在 left 和 mid 之間,則移動我們的right,完全有序的一個區間內查詢 if (nums[mid] > target && target >= nums[left]) { right = mid - 1; // target 落在right和 mid 之間,有可能在陣列1, 也有可能在陣列2 } else if (target > nums[mid] || target < nums[left]) { left = mid + 1; } //不落在同一陣列的情況,left 在陣列1, mid 落在 陣列2 }else if (nums[mid] < nums[left]) { //有序的一段區間,target 在 mid 和 right 之間 if (nums[mid] < target && target <= nums[right]) { left = mid + 1; // 兩種情況,target 在left 和 mid 之間 } else if (target < nums[mid] || target > nums[right]) { right = mid - 1; } } } //沒有查詢到 return -1; } } ``` 大家如果整透了這道題目,可以結合的上篇文章和這篇文章,去做一下 leetcode81 題,算是對這個題目的升級。 下面我們來看一下另外一種變體,如何在二維矩陣裡使用二分查詢呢? 其實這個很簡單,只要學會了二分查詢,這個完全可以解決,我們先來看一個例子 我們需要從一個二維矩陣中,搜尋是否含有元素 7,我們如何使用二分查詢呢?其實我們可以完全將二維矩陣想象成一個有序的一維陣列,然後用二分,,比如我們的二維矩陣中,共有 9 個元素,那定義我們的 left = 0,right = 9 - 1= 8,是不是和一維陣列定義相同,然後我們求我們的 mid 值, mid = left +((right - left) >> 1)此時 mid = 4 ,但是我們的二維矩陣下標最大是,nums[2,2]呀,你這求了一個 4 ,讓我們怎麼整呀。如果我們理解了二分查詢,那麼這個題目考察我們的應該是如何將一維陣列的下標,變為 二維座標。其實也很簡單,咱們看哈,此時咱們的 mid = 4,咱們的二維矩陣共有 3行, 3列,那我們 mid =4,肯定在第二行,那麼這個應該怎麼求得呢? 我們可以直接用 (mid/列數),即可,因為我們 mid = 4,4 /3 = 1,說明在 在第二行,那如果 mid = 7 ,7/3=2,在第三行,我們第幾行知道了,那麼我們如何知道第幾列呢?我們可以直接根據 (mid % 列數 )來求得呀,比如我們此時 mid = 7,7%3 = 1,那麼在我們一維陣列索引為 7 的元素,其處於二維陣列的第2列,大家看看下圖是不是呀! ![二維陣列](https://content.markdowner.net/pub/rO5Ww2-aVNRmA1) 下面我們來看一下 leetcode 74題,讓我們給他整個通透 ### 搜尋二維矩陣 #### 題目描述 編寫一個高效的演算法來判斷 m x n 矩陣中,是否存在一個目標值。該矩陣具有如下特性: 每行中的整數從左到右按升序排列。 每行的第一個整數大於前一行的最後一個整數。 示例1 > 輸入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3 > 輸出:true 示例2 > 輸入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13 > 輸出:false 示例3 > 輸入:matrix = [], target = 0 > 輸出:false #### 題目解析 在上面我們已經解釋瞭如何在二維矩陣中進行搜尋,這裡我們再對其進行一個總結,就是我們憑空想象一個一維陣列,這個陣列是有二維陣列一層一層拼接來的,也是完全有序,然後我們定義兩個指標一個指向一維陣列頭部,一個指向尾部,我們求得 mid 值然後將 mid 變成二維座標,然後和 target 進行比較,如果大於則移動 left ,如果小於則移動 right 。 #### 題目程式碼 ```java class Solution { public boolean searchMatrix(int[][] matrix, int target) { if (matrix.length == 0) { return false; } //行數 int row = matrix.length; //列數 int col = matrix[0].length; int left = 0; //行數乘列數 - 1,右指標 int right = row * col - 1; while (left <= right) { int mid = left+ ((right-left) >> 1); //將一維座標變為二維座標 int rownum = mid / col; int colnum = mid % col; if (matrix[rownum][colnum] == target) { return true; } else if (matrix[rownum][colnum] > target) { right = mid - 1; } else if (matrix[rownum][colnum] < target) { left = mid + 1; } } return false; } } ``` 我是袁廚,一個酷愛用動圖解演算法的年輕人,一個酷愛做飯的程式設計師,一個想和你一起進步的小老弟。 ![](https://content.markdowner.net/pub/rO5Ww2-E