【二分】LeetCode 33. 搜索旋轉排序數組【中等】

整數數組 nums 按升序排列,數組中的值 互不相同 。

在傳遞給函數之前,nums 在預先未知的某個下標 k(0 <= k < nums.length)上進行了 旋轉,使數組變爲 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下標 從 0 開始 計數)。例如, [0,1,2,4,5,6,7] 在下標 3 處經旋轉後可能變爲 [4,5,6,7,0,1,2] 。

給你 旋轉後 的數組 nums 和一個整數 target ,如果 nums 中存在這個目標值 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
 

提示:

1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums 中的每個值都 獨一無二
題目數據保證 nums 在預先未知的某個下標上進行了旋轉
-10^4 <= target <= 10^4
 

進階:你可以設計一個時間複雜度爲 O(log n) 的解決方案嗎?

分析

方法一:二分查找

對於有序數組,可以使用二分查找方法查找元素。

但是這道題目中,數組本身不是有序的,進行旋轉後只保證了數組的局部是有序的,這樣還能進行二分查找嘛?答案是可以的。

我們發現,我們將數組從中間分開成左右兩部分的時候,一定有部分數組是有序的。拿示例來看,我們從6這個位置分開以後數組變成了[4,5,6] 和 [7,0,1,2]兩個部分,其中左邊[4,5,6]這個部分的數組是有序的,其他也是如此。

這啓示我們可以在常規二分查找的時候查看當前mid爲分割位置分割出來的兩個部分[1, mid]和[mid+1, r]哪個部分是有序的,並根據有序的那個部分確定我們改該如何改變二分查找的上下界,因爲我們能夠根據有序的那部分判斷出target在不在這個部分:

(1)如果[1, mid-1]是有序數組,且target的大小滿足[nums[l], nums[mid]),則我們應該將搜索範圍縮小至[l, mid-1],否則在[mid+1, r]中尋找。

(2)如果[mid, r]是有序數組,且target的大小滿足(nums[mid+1], nums[r]],則我們應該將搜索範圍縮小至[mid+1, r],否則在[l, mid-1]。

 

需要注意的是,二分的寫法有很多種,所以在判斷target大小與有序部分的關係的時候可能會出現細節上的差別。

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

            if nums[0] <= nums[mid]:
                if nums[0] <= target <= nums[mid]:
                    r = mid - 1
                else:
                    l = mid + 1
            else:
                if nums[mid] < target <= nums[len(nums) - 1]:
                    l = mid + 1
                else:
                    r = mid - 1
        return -1

時間複雜度:O(log n),其中n爲nums數組的大小。整個算法時間複雜度即爲二分查找的時間複雜度O(log n)。

空間複雜度:O(1),只需要常數級別的空間存放量。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章