一文解決 4 道「搜索旋轉排序數組」題


轉載自:leetcode題解區-一文解決 4 道「搜索旋轉排序數組」題

本文涉及 4 道「搜索旋轉排序數組」題:

可以分為 3 類:

  • 33、81 題:搜索特定值
  • 153、154 題:搜索最小值
  • 81、154 題:包含重復元素

33. 搜索旋轉排序數組

題目要求時間復雜度$O(logn)$,顯然應該使用二分查找。二分查找的過程就是不斷收縮左右邊界,而怎么縮小區間是關鍵。

如果數組「未旋轉」,在數組中查找一個特定元素 target 的過程為:

  • 若 target == nums[mid],直接返回
  • 若 target < nums[mid],則 target 位於左側區間 [left,mid) 中。令 right = mid-1,在左側區間查找
  • 若 target > nums[mid],則 target 位於右側區間 (mid,right] 中。令 left = mid+1,在右側區間查找

但是這道題,由於數組「被旋轉」,所以左側或者右側區間不一定是連續的。在這種情況下,如何判斷 target 位於哪個區間?

首先,一個重要的結論:將區間分均分,必然有一半有序,一半無序。問題是如何找到有序的那一半?

根據旋轉數組的特性,當元素不重復時,如果 nums[i] <= nums[j],說明區間 [i,j] 是「連續遞增」的

因此,在旋轉排序數組中查找一個特定元素時:

  • 若 target == nums[mid],直接返回
  • 若 nums[left] <= nums[mid],說明左側區間 [left,mid]「連續遞增」。此時:
    • 若 nums[left] <= target < nums[mid],說明 target 位於左側。令 right = mid-1,在左側區間查找
    • 否則,令 left = mid+1,在右側區間查找
  • 否則,說明右側區間 [mid,right]「連續遞增」。此時:
    • 若 nums[mid] < target <= nums[right],說明 target 位於右側區間。令 left = mid+1,在右側區間查找
    • 否則,令 right = mid-1,在左側區間查找
  • 注意:區間收縮時不包含 mid,也就是說,實際收縮后的區間是 [left,mid) 或者 (mid,right]

可以很容易地寫出代碼:

int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size()-1, mid;
    while(left <= right)
    {
        int mid = (left+right) >> 1;
        if(nums[mid] == target)  return mid;
        if(nums[left] <= nums[mid])
        {
            if(nums[left] <= target && target < nums[mid])  right = mid-1;
            else left = mid+1;
        }
        else
        {
            if(nums[mid] < target && target <= nums[right])  left = mid+1;
            else right = mid-1;
        }
    }
    return -1;
}

81. 搜索旋轉排序數組-ii

這道題是 33 題的升級版,元素可以重復。當 nums[left] == nums[mid] 時,無法判斷 target 位於左側還是右側,此時無法縮小區間,退化為順序查找。

例如 [1, 3, 1, 1, 1]中查找3,按原來的代碼就會出錯。

順序查找的一種方法是直接遍歷 [left,right] 每一項:

if nums[left] == nums[mid] {
    for i := left; i <= right; i++ { 
        if nums[i] == target {
            return i
        }
}

另一種方法是令 left++,去掉一個干擾項,本質上還是順序查找:

if nums[left] == nums[mid] {
    left++
    continue
}

其實這道題沒有低於O(n)的算法,所以直接遍歷一遍即可。

153. 搜索旋轉排序數組中的最小值

如果數組沒有翻轉,即 nums[left] <= nums[right],則 nums[left] 就是最小值,直接返回。

如果數組翻轉,需要找到數組中第二部分的第一個元素:

 

下面討論數組翻轉的情況下,如何收縮區間以找到這個元素:

  • 若 nums[left] <= nums[mid],說明區間 [left,mid] 連續遞增,則最小元素一定不在這個區間里,可以直接排除。因此,令 left = mid+1,在 [mid+1,right] 繼續查找

  • 否則,說明區間 [left,mid] 不連續,則最小元素一定在這個區間里。因此,令 right = mid,在 [left,mid] 繼續查找
  • [left,right] 表示當前搜索的區間。注意 right 更新時會被設為 mid 而不是 mid-1,因為 mid 無法被排除。這一點和「33 題 查找特定元素」是不同的
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size()-1, mid;
        while(left <= right)
        {
            if(nums[left] <= nums[right])  return nums[left];   // 如果整個區域遞增
            mid = (left + right)>>1;
            if(nums[left] <= nums[mid])
                left = mid+1;
            else
                right = mid;  // mid是有可能的
        }
        return -1;
    }

154. 搜索旋轉排序數組中的最小值-ii

這道題是 153 題的升級版,元素可以重復。和 81 題一樣,當 nums[left] == nums[mid] 時,退化為順序查找。

81 題提供了兩種方法:

  • 一種是直接遍歷 [left,right] 每一項
  • 另一種是 left++,跳過一個干擾項

154 題只能使用第一種方法。因為如果 left 是最小元素,那么 left++ 就把正確結果給跳過了。


免責聲明!

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



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