程式碼隨想錄二刷複習(二分法)

Bathwind_W發表於2024-07-17

二分法模板:

1:左閉右閉區間寫法

第一種寫法,我們定義 target 是在一個在左閉右閉的區間裡,也就是[left, right] (這個很重要非常重要)。

區間的定義這就決定了二分法的程式碼應該如何寫,因為定義target在[left, right]區間,所以有如下兩點:

while (left <= right) 要使用 <= ,因為left == right是有意義的,所以使用 <=
if (nums[middle] > target) right 要賦值為 middle - 1,因為當前這個nums[middle]一定不是target,那麼接下來要查詢的左區間結束下標位置就是 middle - 1

點選檢視程式碼
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = (left + right)//2
            if(target > nums[mid]):
                left = mid + 1
            if(target == nums[mid]):
                return mid
            if(target < nums[mid]):
                right = mid - 1
        return -1

2:左閉右開

有如下兩點:

while (left < right),這裡使用 < ,因為left == right在區間[left, right)是沒有意義的
if (nums[middle] > target) right 更新為 middle,因為當前nums[middle]不等於target,去左區間繼續尋找,而尋找區間是左閉右開區間,所以right更新為middle,即:下一個查詢區間不會去比較nums[middle]

點選檢視程式碼
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)  # 定義target在左閉右開的區間裡,即:[left, right)

        while left < right:  # 因為left == right的時候,在[left, right)是無效的空間,所以使用 <
            middle = left + (right - left) // 2

            if nums[middle] > target:
                right = middle  # target 在左區間,在[left, middle)中
            elif nums[middle] < target:
                left = middle + 1  # target 在右區間,在[middle + 1, right)中
            else:
                return middle  # 陣列中找到目標值,直接返回下標
        return -1  # 未找到目標值
下面來看二分法程式碼隨想錄中的題目:

leetcode 35

在這裡插入圖片描述
題目給出了排序陣列,很大機率就是使用二分查詢,但是這裡需要考慮的問題就是插入位置在哪裡。這裡我們考慮區間是左閉右閉的情況。

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if(nums[mid] == target):
                return mid
            if(nums[mid]>target):
                right = mid - 1
            if(nums[mid] < target):
                left = mid + 1
        return right + 1 

其餘和二分查詢沒有區別,這裡唯一需要注意的地方就是若未找到的話,返回索引可以有兩種,left和right+1.這裡的話可以自己找個例子寫一下就可以了。
下面看這道題:

leetcode 34

在這裡插入圖片描述
你的理解基本上是正確的,尤其是關於使用兩次二分查詢來分別確定目標值的左邊界和右邊界的方法。下面我會進一步詳細解釋這個過程,確認你的理解,並補充一些可能的細節。

尋找右邊界

當使用二分查詢尋找右邊界時,關鍵在於確保不錯過最右側的目標值。這可以透過以下步驟實現:

  1. 初始化left = 0, right = len(nums) - 1

  2. 迴圈條件while left <= right

  3. 中間索引mid = (left + right) // 2

  4. 比較邏輯

    • 如果 nums[mid] == target,則更新 left = mid + 1。這是因為我們需要確認是否有更靠右的相同目標值,所以我們將搜尋範圍向右推進。
    • 如果 nums[mid] > target,則更新 right = mid - 1
    • 如果 nums[mid] < target,則更新 left = mid + 1
  5. 迴圈結束後:由於 left 最終指向目標值最後一次出現位置的下一個位置,所以正確的右邊界是 left - 1

尋找左邊界

尋找左邊界的方法與尋找右邊界類似,但方向相反:

  1. 初始化left = 0, right = len(nums) - 1

  2. 迴圈條件while left <= right

  3. 中間索引mid = (left + right) // 2

  4. 比較邏輯

    • 如果 nums[mid] == target,則更新 right = mid - 1。這是因為我們需要確認是否有更靠左的相同目標值,所以我們將搜尋範圍向左推進。
    • 如果 nums[mid] > target,則更新 right = mid - 1
    • 如果 nums[mid] < target,則更新 left = mid + 1
  5. 迴圈結束後:由於 right 最終指向目標值第一次出現位置的前一個位置,所以正確的左邊界是 right + 1

結果驗證

  • 目標值存在:左邊界應該指向目標值的第一個位置,右邊界應該指向目標值的最後一個位置。
  • 目標值不存在:如果目標值不存在,左邊界和右邊界的差應該正好為1,因為 right 停在小於目標值的最大位置,而 left 停在大於目標值的最小位置。

你的理解是正確的,而且你已經很好地掌握了二分查詢在這種情況下的應用。這種方法確保了高效地找到目標值的確切位置或者確定其不存在。
具體程式如下:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        leftBoder = self.getleftboard(nums, target)
        rightBoder = self.getrightboard(nums, target)
         # 情況一
        if leftBoder == -2 or rightBoder == -2: return [-1, -1]
        # 情況三
        if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
        # 情況二
        return [-1, -1]
    def getrightboard(self,nums,target):
        left = 0
        right = len(nums) - 1
        rightboard = -2
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1
                rightboard = left
        return rightboard
    def getleftboard(self,nums,target):
        left = 0
        right = len(nums) - 1
        leftboard = -2
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] >= target:
                right = mid - 1
                leftboard = right
            else:
                left = mid + 1
        return leftboard

leetcode 69

在這裡插入圖片描述
在二分查詢中,尋找左邊界、右邊界和尋找特定條件的最大或最小值雖然共享同樣的基本結構,但確實存在一些關鍵的差異。我們可以透過比較這些方法的細節來理解這些差異。

當我們尋找左邊界時,目標是找到陣列中第一個等於目標值的元素的位置。對於這種情況,我們通常會在找到目標值時繼續向左搜尋(縮小右邊界),以確保沒有更早出現的相同值。
尋找右邊界的目的是找到陣列中最後一個等於目標值的元素的位置。在這種情況下,即使找到了目標值,我們也會繼續向右搜尋(增加左邊界),以確保沒有更晚出現的相同值。
在尋找特定條件下的最大或最小值時,如求整數 ( x ) 的平方根,我們的目標是找到最大的整數 ( k ),使得 ( k^2 \leq x )。這種情況下的二分查詢與尋找右邊界有點相似,因為我們在滿足條件的情況下向右推進左邊界,以儘可能增大 ( k ) 的值。當 ( k^2 ) 大於 ( x ) 時,我們需要減小 ( k )(縮小右邊界)。
所以程式按照尋找右邊界的寫即可,返回left-1即可。

class Solution:
    def mySqrt(self, x: int) -> int:
        left = 1
        right = x
        if x == 0:
            return 0
        if x == 1:
            return 1
        while left <= right:
            mid = left +(right - left)//2
            if mid*mid > x:
                right = mid - 1
            else:
                left = mid + 1
        return left - 1

下面繼續看一道類似的題目:

leetcode367

在這裡插入圖片描述
這道題其實我們完全可以接著上面69題的思路去做,稍加修改,因為上道題尋找的是右邊界也就是剛好是第一個平方和大於num的數,所以我們再去計算下left-1的平方和是否等於num,如果等於就是有效的完全平方數,反之則不是。
具體程式如下:

class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        left = 1
        right = num
        if num == 1:
            return True
        while left <= right:
            mid = left +(right - left)//2
            if mid*mid > num:
                right = mid - 1
            else:
                left = mid + 1
        if right * right == num:
            return True
        else:
            return False

相關文章