今天抽時間來和大家一起捋一下二分法查找的解題思路和通用模板。筆者也從leetcode上面找了幾道二分查找的題目來摸出一些類似的規律。
首先,什麼是二分查找?其實質就是折半查找,先縮小一半,再縮小一半,再一步步的縮小範圍,直至找出確定的目標。先看題目一:
https://leetcode.cn/problems/binary-search/
這是一道難度等級爲簡單級別的題目,較爲基礎,以這道經典的題目來展開深入;
解題思路:首先將這個列表中的元素,分爲兩組,左邊一半,右邊一半,然後將中間值與相鄰的元素比較大小,
如果中間值比目標值大,那就在左邊的一半里繼續找;
如果中間值比目標值小,那就在右邊的一半里繼續找:
如果中間值等於目標值,那就直接返回下標值index
如果什麼也找不到,那就在外面直接返回-1
""" 題目:給定一個n個元素有序的(升序)整型數組nums 和一個目標值target,寫一個函數搜索nums中的target,如果目標值存在返回下標,否則返回 -1 輸入: nums = [-1,0,3,5,9,12], target = 9 輸出: 4 解釋: 9 出現在 nums 中並且下標爲 4 輸入: nums = [-1,0,3,5,9,12], target = 2 輸出: -1 解釋: 2 不存在 nums 中因此返回 -1 """ def search(nums, target): left, right = 0, len(nums) - 1 # 雙閉區間 while left <= right: # 由於是雙閉區間,決定了當退出循環的條件是left>right的時候,如果不帶等號可能會遺漏 mid = left + (right - left) // 2 # 可以防止left+right過大導致加和溢出問題 # 其實,求中間值的方法,mid = (left+right)//2 也是可以的,等同於上面那一行,但是可能會出現數據過大而造成的溢出問題 if nums[mid] == target: # 找到目標即可返回 return mid elif nums[mid] < target: # 如果中間值比目標值小,比較過後,那就可以直接在另一半里面找了,之前左側那一半元素可以直接棄掉,此時以left=mid+1爲左側邊界,重新開始找 left = mid + 1 elif nums[mid] > target: # 同理,如果中間值比目標值大,比較過後,那就可以直接在另一半里面找了,之前右側那一半元素可以直接棄掉,此時以right=mid-1爲右側邊界,重新開始找 right = mid - 1 # 如果裏面的判斷條件還查找不到,那就直接返回-1 return -1
我把每一步的執行過程都在稿紙尚一步一步的寫出來,請看以上拍照截圖
請繼續看第二題:
https://leetcode.cn/problems/search-insert-position/
這道題和上一道題幾乎相類似,就是多了一個插入的元素,並顯示下標值
""" 二分查找插入列表中的題目 給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置 輸入: nums = [1,3,5,6], target = 5 輸出: 2 輸入: nums = [1,3,5,6], target = 2 輸出: 1 輸入: nums = [1,3,5,6], target = 7 輸出: 4 """ def searchInsert(nums, target): left, right = 0, len(nums) - 1 # 雙閉區間 while left <= right: # 由於是雙閉區間,決定了當退出循環的條件是left>right的時候,如果不帶等號可能會遺漏 mid = left + (right - left) // 2 # 可以防止left+right過大導致加和溢出問題 if nums[mid] == target: # 找到目標即可返回 return mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid - 1 return right + 1
請繼續看第三題:
https://leetcode.cn/problems/valid-perfect-square/ 有效的完全平方數
""" 二分查找:有效的完全平方數 給定一個 正整數 num ,編寫一個函數,如果 num 是一個完全平方數,則返回 true ,否則返回 false 最好不好使用任何的內置函數sqrt等 """ def isPerfectSquare(self, num: int) -> bool: if num == 0: return True if num == 1: return True left= 0 right = num while left <= right: mid = (left + right) // 2 if mid * mid == num: return True elif mid * mid > num: right = mid - 1 else: left = mid + 1 return False
再看第四題:
https://leetcode.cn/problems/sqrtx/ x的平方數
""" 二分法: 給你一個非負整數 x ,計算並返回 x 的 算術平方根 。 由於返回類型是整數,結果只保留 整數部分 ,小數部分將被 捨去 輸入:x = 4 輸出:2 輸入:x = 8 輸出:2 只保留整數部分 """ def mySqrt(self, x: int) -> int: left = 0 right = x while left <= right: mid = left + (right - left) // 2 # 確定命中區間 if mid * mid <= x: if (mid + 1) * (mid + 1) <= x: # 如果下一個元素的平方還小於x 則假命中 left = mid + 1 else: return mid # 否則 真命中 直接返回 else: right = mid - 1
請看最後一題:
https://leetcode.cn/problems/peak-index-in-a-mountain-array/ 山脈數組
""" 山脈數組 輸入:arr = [0,2,1,0] 輸出:1 輸入:arr = [0,10,5,2] 輸出:1 輸入:arr = [3,4,5,1] 輸出:2 輸入:arr = [24,69,100,99,79,78,67,36,26,19] 輸出:2 """ def peakIndexInMountainArray(arr): left = 1 right = len(arr) - 2 ans = 0 while left <= right: mid = left + (right - left) // 2 if arr[mid] > arr[mid + 1]: ans = mid right = mid - 1 else: left = mid + 1 return ans
所以從以上5道題中,基本可以摸出二分查找的規律了,left與right的邏輯處理尤爲重要