[LeetCode] 搜索旋轉排序數組


示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例 2:
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1

問題分析

首先個人認為題目中的“旋轉”可能並不直觀,不利於理解,在這里旋轉也可以認為是數組向右循環移動,何為循環移動,看下面

1, 2, 3, 4, 5 循環向右移動一位 為 5, 1, 2, 3, 4
1, 2, 3, 4, 5 循環向右移動兩位 為 4, 5, 1, 2, 3

題目要求時間復雜度O(logN),而且數組是排序的,肯定是二分法了。但是這個數組卻並不是一個完全遞增的數組,他的特點是

  1. 數組中存在一個位置index,A = [0, index)遞增序列,B = [index, size)是一個遞增序列
  2. 遞增序列A中的任意一個元素均大於遞增序列B的任意一個元素,用數學表示的話就是

對於特點2可能不是很明顯,理由如下:

  1. 題中的旋轉可以認為進行了X次的向右循環移動
  2. 每次向右循環移動都會將最后一位數移動到最左邊
  3. 根據 特點1理由2 可知每次被移動到左邊的數字為序列B中最大的元素,移動過后這個元素進入了序列A的最左邊,也就是說這個元素成為了序列A中最小的元素
  4. 根據上述三條理由確定一個在每次循環右移之后都成立的條件:

每次循環右移到數組最左邊的元素都是大於遞增序列B的任何一個數的,且這個元素是遞增序列A中最小的一個元素

或者可以表述為

每次循環右移到遞增序列A最左邊的元素都是大於遞增序列B中的任何一個元素,且這個元素是遞增序列A中最小的一個元素

  1. 所以進行了X次向右循環移動的數組依舊滿足 理由4 的結論,所以 特點2 成立

解法一:數組中的最小值,對左右兩邊各進行一次二分查找

很明顯,數組的最小值就是遞增序列B的第一項。最小值的左邊(不包括旋最小值)是一個遞增序列,最小值的右邊(包括最小值)是一個遞增數列。

不是很清楚的讀者可以在下圖,紅線圈住的值就是最小值,也可以很輕易地看出上述規律。

由於題目要求時間復雜度O(lgn),所以我們必須在O(lgn)的時間內找到最小值,具體如何找呢?肯定還是二分搜索的思想,不過要分情況考慮了。

當 nums[mid] > nums[0] 時

應該是下圖所示的情況,很明顯,最小是在二分點的右邊,所以應該是移動左指針,即

left = mid + 1;

當 nums[mid] < nums[0] 時

應該是下圖所示的情況,很明顯,最小值在二分點的左邊,應該移動右指針,即

right = mid - 1;

不過我們少考慮的一種情況,也就是如果整個數組是一個完全遞增的數組,也就是並沒有經過任何變化,那么上述方法的到的最終結果就是會出出錯,所以在二分結果后需要再做一個判斷,判斷數組的第一位和二分查找得到的結果那個小,即

if nums[0] < nums[mid]
    minIndex = 0;
else
    minIndex = mid;

所以尋找旋轉點的最終偽代碼為:

findMin(nums)
    // nums is a array

    left = 0;
    right = nums.length - 1;

    while left <= right
        mid = (left + right) / 2;
        // 判斷是否是最小值,也就是左右都比它大
        if nums[mid] is smaller than left and right values
            break;
        else if nums[mid] > nums[0]
            left = mid + 1;
        else
            right = mid - 1;

    if nums[0] < nums[mid]
        minIndex = 0;
    else
        minIndex = mid;
    return midIndex;

現在我們找到了最小值了,就可以以最小值為分界點分別對左右兩個遞增序列進行二分查找了,所以整個程序的偽碼如下:

search(nums,target)
    /*
        nums is a array
        target is a number
    */

    minIndex = findMin(nums);

    left = 0;
    right = minIndex - 1;

    while left <= right
        mid = (left + right) / 2;
        if nums[mid] == target
            return mid;
        else if target < nums[mid]
            right = mid - 1;
        else
            left = mid + 1;

    left = minIndex;
    right = nums.length - 1;

    while left <= right
        mid = (left + right) / 2;
        if nums[mid] == target
            return mid;
        else if target < nums[mid]
            right = mid - 1;
        else
            left = mid + 1;

時空復雜度分析

找出旋轉點O(lgn),兩次二分查找均為O(lgn),算法總時間復雜度為O(lgn)

算法使用了常數空間,空間復雜度為O(1)

解法二:直接通過二分法找出目標值

這里會稍微復雜一些,依舊先分情況討論

當 nums[mid] > nums[0] 且 nums[mid] < target

從圖上來看,二分點在目標值的左邊,所以應該移動左指針,即

left = mid + 1;

當 nums[mid] > nums[0] 且 nums[mid] > target 且 nums[0] < target

從圖上看,二分點在目標值右邊,所以應該移動右指針,即

right = mid - 1;

當 nums[mid] > nums[0] 且 nums[mid] > target 且 nums[0] > target

從圖上來看,二分點在目標值的左邊,應該移動左指針,即

left = mid + 1;

當 nums[mid] < nums[0] 且 nums[mid] > target

從圖上來看,二分點在目標值的右邊,應該移動右指針,即

right = mid - 1;

當 nums[mid] < nums[0] 且 nums[mid] < target 且 nums[0] > target

從圖上來看,二分點在目標值的左邊,應該移動左指針,即

left = mid + 1;

當 nums[mid] < nums[0] 且 nums[mid] < target 且 nums[0] < target

從圖上來看,二分點在目標值的右邊,應該移動右指針,即

right = mid - 1;

當 nums[mid] = nums[0]

從圖上來看,目標值肯定在二分點的右邊(暫不考慮目標值在最左端的情況),應該移動左指針,即

left = mid + 1;

當目標值位於數組兩端

在程序最開始時做判斷即可

所以整個程序的偽代碼應該為:

search(nums, target)
    /*
        nums is a array
        target is a number
    */

    left = 0;
    right = nums.length - 1;
    if nums[0] == target
        return 0;
    if nums[nums.length - 1] == target
        return nums.length - 1;


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

            else if nums[mid] < nums[0]
                if target < nums[mid]
                    right = mid - 1;
                else
                    if target < nums[nums.size() - 1]
                        left = mid + 1;
                    else
                        right = mid - 1;
            else
                left = mid + 1;
    
    return -1;

時空復雜度分析

時間復雜度為O(lgn),空間復雜度O(1)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM