題目描述:
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [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的索引這一步,就是典型的二分法操作了,十分容易。