1. 程式人生 > 其它 >81. 搜尋旋轉排序陣列 II

81. 搜尋旋轉排序陣列 II

技術標籤:LeetCode

81. 搜尋旋轉排序陣列 II

題目描述

假設按照升序排序的陣列在預先未知的某個點上進行了旋轉。

( 例如,陣列 [0,0,1,2,2,5,6] 可能變為 [2,5,6,0,0,1,2] )。

編寫一個函式來判斷給定的目標值是否存在於陣列中。若存在返回 true,否則返回 false

示例 1:

輸入: nums = [2,5,6,0,0,1,2], target = 0
輸出: true

示例 2:

輸入: nums = [2,5,6,0,0,1,2], target = 3
輸出: false

進階:

  • 這是 搜尋旋轉排序陣列 的延伸題目,本題中的 nums 可能包含重複元素。
  • 這會影響到程式的時間複雜度嗎?會有怎樣的影響,為什麼?

題解:

參考 搜尋旋轉排序陣列 一題,這題增加了一個條件:可以有重複值,這個條件有點麻煩。。。

還是用二分來搞定。

注意:二分不一定必須要有單調性,二分的本質是尋找某種性質的分界點。只要找到某種性質,可以確定目標在區間的前半部分還是後半部分,就可以用二分找到這個分界點。

為了便於分析,將陣列中的元素畫在二維座標系中,橫軸代表下標,縱軸代表值。

在這裡插入圖片描述

水平的實線表示相同的元素。可以發現,除了黑色水平那段,其餘部分均滿足二分性質:豎直的虛線左邊的元素均滿足 a r r [ i ] > = a r r [ 0 ] arr[i]>=arr[0]

arr[i]>=arr[0],而虛線右邊(除了黑色部分)元素均滿足 a r r [ i ] < a r r [ 0 ] arr[i]<arr[0] arr[i]<arr[0]

首先,我們需要把黑色的水平段刪除,至於為什麼刪除呢?考慮一種情況: a r r [ m i d ] = = a [ 0 ] arr[mid] == a[0] arr[mid]==a[0],這種情況下,根本不知道往哪邊走,所以只能刪除。

還需要處理陣列未旋轉的情況,當刪除黑色水平段後,若剩下的最後一個元素大於等於arr[0],說明陣列完全單調。

最壞情況:元素均相等情況下的時間複雜度為 O ( n ) O(n)

O(n)

法一:

兩次二分。參考 在有序旋轉陣列中找到最小值 一題,如果我們可以找到分界點,就可以確定 target 在左右哪個區間,然後直接在該區間上進行二分查詢即可。

把複雜問題分解成簡單的子問題 思想很重要。

class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int n = nums.size();
        if ( !n ) return false;
        n -= 1;
        while ( n && nums[n] == nums[0] ) --n;
        if ( !n ) return nums[0] == target;
        if ( nums[0] < nums[n] ) {
            if ( target < nums[0] || target > nums[n] ) 
                return false;
        }
        int l = 0, r = n, m;
        if ( nums[0] > nums[n] ) {
            while ( l < r ) {
                m = ( l + r ) >> 1;
                if ( nums[m] == target ) return true;
                if ( nums[m] >= nums[0] ) l = m + 1;
                else r = m;
            }
            if ( target <= nums[n] ) r = n;
            else l = 0, --r;
        }
        while ( l < r ) {
            m = ( l + r ) >> 1;
            if ( nums[m] == target ) return true;
            if ( nums[m] > target ) r = m;
            else l = m + 1;
        }
        return nums[r] == target;
    }
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:29.95%
*/
法二:

在二分過程中,通過一些條件判斷 target 在哪個區間也行:

  • n u m s [ m i d ] > = n u m s [ 0 ] nums[mid] >= nums[0] nums[mid]>=nums[0],說明 mid 在左邊區間:
    1. t a r g e t > = n u m s [ l ] a n d t a r g e t < = n u m s [ m i d ] target >= nums[l] \quad and \quad target <= nums[mid] target>=nums[l]andtarget<=nums[mid],說明此時要在區間 [l,mid] 查詢, r = mid
    2. t a r g e t < n u m s [ l ] target < nums[l] target<nums[l],說明 target 在右邊區間,l = mid + 1
    3. t a r g e t > n u m s [ m i d ] target > nums[mid] target>nums[mid],說明 targetmid 右邊,l = mid + 1
  • n u m s [ m i d ] < n u m s [ 0 ] nums[mid] < nums[0] nums[mid]<nums[0],說明 target 在右邊區間:
    1. t a r g e t < = n u m s [ m i d ] target <= nums[mid] target<=nums[mid],說明 targetmid 左邊,r = mid
    2. t a r g e t > n u m s [ n ] target > nums[n] target>nums[n],說明 target 在左邊區間,r = mid
    3. 否則的話,說明 targetmid 右邊,l = mid + 1
class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int n = nums.size();
        if ( !n ) return false;
        n -= 1;
        while ( n && nums[n] == nums[0] ) --n;
        if ( !n ) return nums[0] == target;
        if ( nums[0] < nums[n] ) {
            if ( target < nums[0] || target > nums[n] ) 
                return false;
        }
        int l = 0, r = n, m;
        while ( l < r ) {
            m = ( l + r ) >> 1;
            if ( nums[m] == target ) return true;
            if ( nums[m] >= nums[0] ) {
                if ( nums[0] <= target && target <= nums[m] ) r = m;
                else l = m + 1;
            } else {
                if ( target <= nums[m] || target > nums[n] ) r = m;
                else l = m + 1;
            }
        }
        return nums[r] == target;
    }
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:30.23%
*/
法三:

直接掃描一遍 nums 。。。

class Solution {
public:
    bool search(vector<int>& nums, int target) {
        for ( auto& it : nums )
            if ( it == target ) return true;
        return false;
    }
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:22.29%
*/

三種方法記憶體消耗簡直離譜。。。