1. 程式人生 > 其它 >二分查詢的本質,搜尋旋轉排序陣列問題

二分查詢的本質,搜尋旋轉排序陣列問題

整數陣列 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
作者:力扣 (LeetCode)
連結:

https://leetcode-cn.com/leetbook/read/top-interview-questions-medium/xvyz1t/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

二分查詢

剛拿到這道題,想到既然是旋轉了一次的陣列,那麼肯定在旋轉中心的兩側元素是區域性有序的如:4567和012,要是我們能找到這個中心,然後在兩側進行二分搜尋就好了。但其實如果尋找中心只能順序查詢,那麼時間複雜度已經是O(n)了為什麼不直接找target呢?

其實如果明白了二分查詢的本質——根據有序元素段判斷元素位置以不斷收縮邊界選擇搜尋空間,這道題也可以很輕鬆的想出來解法。比如,對於一個有序陣列[1,2,3,4,5,6,7]。mid = 4,如果我們要查詢元素6會很自然的去他的右端尋找[mid+1, left]。因為我們知道mid左側的元素有序,故這些元素都比mid 4小,自然也就比6小了。
那麼對於[4,5,6,7,0,1,2],可以想象,無論mid落到哪,在其兩端都至少有一個區域性有序段

。假如mid為元素6,那麼左側456有序。若果mid為元素0,那麼元素012有序。我們就可以利用有序段和當前target元素的比較,去排除一部分元素從而收縮邊界繼續尋找。只是我們的收縮邊界部分有一些和原版二分查詢不同地方。對於小於6的元素,因為只是區域性有序,所以其左面有45,最右邊也有012,故不能直接移動邊界,還要判斷一下元素是否在有序段內才可以。
程式碼:

public int search(int[] nums, int target) {
        int l = 0, r = nums.length - 1;
        int mid = -1;
        while(l <= r){
            mid = l + (r - l) / 2;
            if(target == nums[mid]){
                return mid;
            }
            //mid至少會有一端有序,可以利用有序段收縮邊界。
            if(nums[mid] >= nums[l]){//左端有序 
                if(target < nums[mid] && target >= nums[l]){ //元素在左端
                    r = mid - 1;
                }else{ //排除左端
                    l = mid + 1;
                }
            }else{//右端有序
                if(target > nums[mid] && target <= nums[r]){ //元素在右端
                    l = mid + 1;
                }else{ //排除右端
                    r = mid - 1;
                }
            }
        }
        return -1;
    }