leetcode-33-搜索旋轉排序數組


題目描述:

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

( 例如,數組 [0,1,2,4,5,6,7] 可能變為 [4,5,6,7,0,1,2] )。

搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。

你可以假設數組中不存在重復的元素。

你的算法時間復雜度必須是 O(log n) 級別。

示例 1:

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

示例 2:

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

 

要完成的函數:

int search(vector<int>& nums, int target) 

 

說明:

1、這道題給定一個vector和一個target整數,vector里面裝着一個升序數組,只不過現在這個升序數組在某個節點上旋轉了。

比如[4,5,6,7,0,1,2],原本就是一個升序數組,現在旋轉了,要求用O(logn)的時間復雜度找到target在vector中的索引。

比如上面的vector中,給定target為0,那么索引就是4。

如果在vector中找不到target,那么返回-1。

 

2、這道題不同於以往的整個升序數組,直接二分法就可以找到索引。

不過現在仍然要O(logn)的時間復雜度,那么我們也可以用二分法找到旋轉節點的索引,接着判斷target在旋轉節點的哪邊,然后用二分法再次找到target的索引。

代碼如下:(附詳解)

    int search(vector<int>& nums, int target) 
    {
        int s1=nums.size(),front=0,behind=s1-1,med=(behind+front)/2;
        if(s1==0)return -1;//如果沒有元素,那么返回-1
        if(s1==1)//如果只有一個元素,那么判斷是不是target的數值
        {
          if(nums[0]==target)//如果是,那么返回0
          return 0;
          return -1;//不是就返回-1
	    }
        while(front<behind)//找到旋轉節點的索引,最后存儲在med中
        {
            if(nums[front]<nums[behind])//如果front的元素數值小於behind的                       
          {               //那么說明整一段都是升序的
        	    med=front;              //那么front就是我們要的旋轉節點的索引
		        break;          //這是一個邊界條件,看不懂的先看下面的if語句
            }
            if(nums[med]<nums[med-1]&&nums[med]<nums[med+1])//滿足條件,med就是我們要的旋轉節點
                break;
            if(nums[med]>nums[front]||nums[med]>nums[behind])//如果med對應的數值大於front對應的數值
            {                          //或者med對應的數值大於behind對應的數值
                front=med+1;                  //那么說明旋轉節點在med前面
                med=(front+behind)/2;              
            }
            else
            {
         behind=med-1;
         med=(front+behind)/2;
            }
        }
        front=0,behind=s1-1;//找到旋轉節點的索引之后,我們分兩段,看一下target在哪一段
        if(nums[front]<=target&&target<=nums[med-1])//用典型的二分法找到target的索引
        {
            behind=med-1;
            while(front<=behind)
            {
                med=(front+behind)/2;
                if(nums[med]==target)
                    return med;
                else if(nums[med]<target)
                    front=med+1;
                else
                    behind=med-1;
            }
            return -1;//如果找不到,那么返回-1
        }
        else if(nums[med]<=target&&target<=nums[behind])//用典型的二分法找到target的索引
        {
            front=med;
            while(front<=behind)
            {
                med=(front+behind)/2;
                if(nums[med]==target)
                    return med;
                else if(nums[med]<target)
                    front=med+1;
                else
                    behind=med-1;
            }
            return -1;//如果找不到,那么返回-1
        }
        return -1;//target沒有在旋轉節點兩邊的任何一段之中,那么返回-1
    }

在通過while循環找到旋轉節點的索引的代碼中,我們先明確我們要找的旋轉節點是“既小於左邊的數值,又小於右邊的數值”。

比如[4,5,6,7,0,1,2]中,0就是我們要找的旋轉節點。

 

所以判斷nums[med]<nums[med-1]&&nums[med]<nums[med+1]這個條件有沒有滿足,如果滿足,那么就是旋轉節點。

如果不滿足,那么看一下nums[med]會不會大於nums[front],大於的話那么說明前半段是升序排列,那么旋轉節點必然在后半段。

但為什么還要加上nums[med]會不會大於nums[behind]的判斷條件呢?

 

比如[5,1,2,3,4],這樣的vector,第一次循環的時候,front是0,behind是4,med是(0+4)/2=2,此時nums[med]不會大於nums[front],所以behind變為1,med改變為(0+1)/2=0。

接着進入第二次循環,nums[med]不會大於nums[front],所以仍然旋轉節點在med的前半段嗎?但我們知道此時旋轉節點應該在med的后半段,也就是為1。

注意到此時med和front都是0,在同一個點上,所以此時可以加上nums[med]跟nums[behind]的比較,來得到一個正確的結論。

 

有同學可能還會疑惑為什么要在找旋轉節點的while循環中,加上nums[front]<nums[behind]的判斷?

比如[4,5,6,7,0,1,2],front為0,behind為6,med為3,也就是元素為7,此時判斷旋轉節點在后半段。front改變為4,behind仍然為6,med為5。

進入下一個循環,此時front對應元素為0,med為1,behind為2。

我們發現旋轉節點的確在[front,behind]的區間中,但是已經不包含任何奇奇怪怪的元素了,整個區間是升序的。

我們也不能再通過nums[med]和nums[front]的比較,或者nums[med]和nums[behind]的比較來得到結論。

此時整個區間升序,那么必然旋轉節點在front這里。

上述代碼實測4ms,beats 99.75% of cpp submissions。

 

總結:

這道題的確很繁瑣,主要在通過二分法找到旋轉節點的索引這一步,要注意各種邊界情況的判斷,總體抽象的思路也要有。

之后通過二分法找到target的索引這一步,就是典型的二分法操作了,十分容易。


免責聲明!

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



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