1. 程式人生 > 其它 >【leetcode-Python】-二分搜尋-34 Find First and Last Position of Element in Sorted Array

【leetcode-Python】-二分搜尋-34 Find First and Last Position of Element in Sorted Array

技術標籤:leetcode

目錄

題目連結

題目描述

示例

解決思路一

解決思路一Python實現

解決思路二

解決思路二Python實現

解決思路三

解決思路三Python實現


題目連結

34. Find First and Last Position of Element in Sorted Array

題目描述

給定非降序整數陣列nums,找出元素值等於target的索引區間。如果陣列中不存在target,返回[-1,-1]。

約束條件:

  • 0 <= nums.length <=10^5
  • -10^9<= nums[i]<=10^9
  • numsis a non-decreasing array.
  • -10^9<= target<=10^9

示例

輸入:nums = [5,7,7,8,8,10],target = 8

輸出:[3,4]

輸入:nums = [5,7,7,8,8,10],target = 6

輸出:[-1,-1]

輸入:nums = [],target = 0

輸出:[-1,-1]

解決思路一

1、先處理特例,當nums為空時,任何target都無法在nums中被找到,因此直接返回[-1,-1]。

2、利用二分搜尋演算法找出目標索引區間的左端點,即陣列中第一個等於target的元素索引值start,如果找不到設定start為-1。

3、找出目標索引區間的右端點,即陣列中最後一個等於target的元素索引值end,如果找不到設定end為-1。具體策略為找出陣列中第一個大於target的元素nums[left],如果nums[left]是陣列的第一個元素,設定end = -1,如果nums[left-1]存在,檢驗nums[left-1]是否等於target,如果不等於說明target在陣列中不存在,則設定end = -1。

解決思路一Python實現

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        #首先找出starting position
        if(len(nums) == 0):
            return [-1,-1]
        left = 0
        right = len(nums)
       
        while(left < right):
            mid = (left + right) // 2
            if(nums[mid] >= target):
                right = mid
            else:
                left = mid + 1
        if(left != len(nums) and nums[left] == target):
            start = left
        else:
            start = -1
        
        #find the ending position 
        left = 0
        right = len(nums)
        while(left < right):
            mid = (left + right) // 2
            if(nums[mid] > target):
                right = mid
            else:
                left = mid + 1
        end = left - 1 #left為num中第一個大於target的索引,該索引減去1的結果可能是元素值等於target的最大索引。
        if(left != 0 and nums[end] != target): #對left進行檢驗,如果end索引能夠訪問並且nums[end]不等於target,說明陣列中不存在target
            end = -1
        return [start,end]

在尋找目標序列的右端點時,如果設定right初始化為len(nums)-1,當left == right 條件滿足跳出迴圈時,需要判斷nums[left]是否等於target(因為有可能right沒有移動,left一直移動到和right重合等於len(nums)-1,nums[left]有可能等於target),如果等於,那麼end應該設為left。程式碼如下,但是因為後續要考慮的東西更多,不推薦這種初始化方式(如果搜尋條件不涉及到對nums[right]的訪問,right變數初始化儘量都設定為len(nums),按照標準配置來寫不容易出現邊界錯誤)。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        #首先找出starting position
        if(len(nums) == 0):
            return [-1,-1]
        left = 0
        right = len(nums)  
        while(left < right):
            mid = (left + right) // 2
            if(nums[mid] >= target):
                right = mid
            else:
                left = mid + 1
        if(left != len(nums) and nums[left] == target):
            start = left
        else:
            start = -1
        
        #find the ending position 
        left = 0
##############right初始化值被修改###################
        right = len(nums)-1
        while(left < right):
            mid = (left + right) // 2
            if(nums[mid] > target):
                right = mid
            else:
                left = mid + 1
        #當left == right時跳出迴圈,如果right被初始化為len(nums)-1,那麼跳出迴圈後需要對nums[left]進行檢驗
        if(nums[left] == target):
            left += 1
        end = left - 1 #left為num中第一個大於target的索引,該索引減去1的結果可能是元素值等於target的最大索引。
        if(left != 0 and nums[end] != target): #對end進行檢驗,如果end索引能夠訪問並且nums[end]不等於target,說明陣列中不存在target
            end = -1
        return [start,end]

解決思路二

1、尋找目標區間的左邊界,並根據左邊界的尋找情況判斷陣列中是否存在target(如果沒有找到左邊界,說明陣列中不存在值等於target的元素)。

2、在左邊界存在的前提下,尋找右邊界(找出陣列中第一個大於target的元素,該元素的索引減去1即為右邊界)。

設定一個findRange函式,傳入nums陣列和is_left變數。如果is_left為True,說明findRange函式找的是

左邊界,當nums[mid]==target時,由right變數暫存mid同時在[left,mid)區間繼續搜尋(因為尋找的是左邊界,左邊界要麼是mid要麼在mid的左邊);如果is_left為False,說明findRange函式尋找的是第一個比target大的元素(右邊界+1),因此當nums[mid]==target時,更新left = mid + 1,在mid右邊進行搜尋。

解決思路二Python實現

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def findRange(nums,is_left):
            left = 0
            right = len(nums)
            while(left<right):
                mid = (left + right) // 2
                if(nums[mid] > target or (nums[mid] == target and is_left)):
                    right = mid
                else:
                    left = mid + 1
            return left
        
        left_index = findRange(nums,is_left = True)
        if left_index == len(nums) or nums[left_index] != target:
            return [-1,-1]
        right_index = findRange(nums,is_left = False)
        return [left_index,right_index-1]

抽取出尋找右邊界的程式碼:

def findRange(nums):
    left = 0
    right = len(nums)
    while(left<right):
        mid = (left + right) // 2
        if(nums[mid] > target):
            right = mid
        else:
            left = mid + 1
    if(left == 0):
        return -1
    return nums[left-1] if nums[left-1] == target else -1

解決思路三

直接利用二分搜尋來尋找右邊界,不需要先找第一個大於target的元素再令其索引值減1。

“用二分搜尋法尋找右邊界”和“用二分搜尋法尋找左邊界”屬於映象問題。用二分搜尋法尋找左邊界的思路為:迴圈跳出條件為left == right,計算mid = (left + right) //2,當nums[mid] >= target時,更新right = mid,由right暫存可能的左邊界;否則更新left = mid + 1。最後對nums[left]進行判斷。程式碼如下:

def findLeftRange(nums):
    left = 0
    right = len(nums)
    while(left<right):
        mid = (left + right) // 2
        if(nums[mid] >= target):
            right = mid
        else:
            left = mid + 1
    if(left != len(nums) and nums[left] == target):   
        return left
    return -1

用二分搜尋演算法尋找右邊界(類似找nums[i]<=target的右邊界)的思路為:迴圈跳出條件為left == right,計算mid = (left + right) // 2 + 1,當nums[mid] > target時,right = mid - 1,當nums[mid] <= target時,left = mid,由left暫存可能的右邊界。mid的計算方式設定為(left + right) // 2 + 1的原因為,當left + 1 == right時,按照mid = (left + right) // 2來計算,mid和left重合,如果滿足nums[mid] <= target,仍然賦值left = mid,這樣left、right都沒有移動,會陷入死迴圈。如果按照 mid =(left + right) // 2 + 1來計算,當left + 1 == right時,mid和right重合,無論nums[mid]和target的大小關係是怎樣的,迴圈都能終止(但需要將right初始化為len(nums)-1,否則當mid和right重合且right = len(nums)時對nums[mid]的訪問會報錯)。

程式碼如下:

def findRightRange(nums):
    left = 0
    right = len(nums)-1 
    while(left<right):
        mid = ((left + right) // 2) + 1
        if(nums[mid] <= target):
            left = mid
        else:
            right = mid - 1
    if(right != -1 and nums[right] == target):   
        return right
    return -1

解決思路三Python實現

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