LeetCode 33-Search in Rotated Sorted Array
题干:
给定一个元素各不相同的升序数组 n u m s nums nums,现将 n u m s nums nums从 k k k位置( k k k未知)旋转(即将从 k k k开始到末尾的部分移到最前面),要求在 l o g ( n ) log(n) log(n)时间内在 n u m s nums nums中寻找元素 t a r g e t target target并返回对应位置(没找到返回 − 1 -1 −1)。
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4
解:
方法1:
一种思路是这样的:画图可以看到,数组满足二段性:有一个分界点 m m m,在 m m m左侧的数都 ≥ n u m s [ 0 ] \ge nums[0] ≥nums[0](设为true),在 m m m右侧的数都 < n u m s [ 0 ] < nums[0] <nums[0](false),在这个条件下,可认为整个数组是有序的(一侧为true,一侧为false),可以通过二分搜索找到这个分界点 m m m(不妨认为 m m m属于左侧),二分搜索即寻找 f ( x ) = = t r u e f(x)==true f(x)==true的右边界;再通过 t a r g e t target target与 n u m s [ 0 ] nums[0] nums[0]的关系判断 t a r g e t target target是属于 m m m左侧部分还是 m m m右侧部分,再进行一次二分搜索(寻找一个数)。
int rightBound(vector<int>& nums, int left, int right, int target){
int ans = nums.size()-1;
while(left < right){
int mid = left + ((right - left) >> 1);
if(nums[mid] >= nums[0]){
ans = mid;
left = mid + 1;
}
else right = mid;
}
return ans;
}
int binarySearch(vector<int>& nums, int left, int right, int target){
int ans = -1;
while(left < right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
ans = mid;
break;
}
else if(nums[mid] < target) left = mid + 1;
else right = mid;
}
return ans;
}
int search(vector<int>& nums, int target) {
// if(nums.size()==1) return nums[0]==target? 0:-1;
int m = rightBound(nums, 0, nums.size(), nums[0]);
if(target >= nums[0]) return binarySearch(nums, 0, m+1, target);
return binarySearch(nums, m+1, nums.size(), target);
}
方法2:
然而,可以不额外寻找这个分界点,直接对旋转数组进行二分。因为这个旋转数组是部分有序的,所以思路就是寻找部分有序的部分,如果 t a r g e t target target在这部分内,就进行二分搜索;如果不在,说明 t a r g e t target target就在无序的那部分,那么就去无序的那部分继续缩小范围,找到有序的子部分来二分搜索。
这是通过比较 n u m s [ l e f t ] nums[left] nums[left]与 n u m s [ m i d ] nums[mid] nums[mid]的大小关系来确定怎么缩小范围的。当 n u m s [ l e f t ] ≤ n u m s [ m i d ] nums[left]\le nums[mid] nums[left]≤nums[mid]时,说明 [ l e f t , m i d ] [left,mid] [left,mid]这部分有序,若 t a r g e t target target位于其中,就在 [ l e f t , m i d − 1 ] [left,mid-1] [left,mid−1]这个区间内继续二分,否则说明 t a r g e t target target不在这个区间,就去 [ m i d + 1 , r i g h t ] [mid+1,right] [mid+1,right]区间继续找;当 n u m s [ l e f t ] > n u m s [ m i d ] nums[left]> nums[mid] nums[left]>nums[mid]时,说明 l e f t left left和 m i d mid mid一定位于分界点 m m m的两侧,那么 [ m i d , r i g h t ] [mid,right] [mid,right]这部分一定有序,就判断 t a r g e t target target是否位于其中,若在,就在 [ m i d + 1 , r i g h t ] [mid+1,right] [mid+1,right]区间内继续二分,否则说明 t a r g e t target target不在这个区间,要去 [ l e f t , m i d − 1 ] [left,mid-1] [left,mid−1]区间继续找。
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
int ans = -1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
ans = mid;
break;
}
if(nums[mid] >= nums[left]){ // [left,mid]这部分是有序的
if(target >= nums[left] && target < nums[mid]){
right = mid - 1;
}
else left = mid + 1;
}
else{ //说明left,mid一定在分界点两侧,可得[mid, right]这部分是有序的
if(target > nums[mid] && target <= nums[right]){
left = mid + 1;
}
else right = mid - 1;
}
}
return ans;
}
方法3:
![58](https://i-blog.csdnimg.cn/blog_migrate/dbaab5cc8b30f240ab8b35b9d273e0b4.jpeg)
方法1的二段性和方法2的找有序区间的组合,若
n
u
m
s
[
m
i
d
]
≥
n
u
m
s
[
0
]
nums[mid] \ge nums[0]
nums[mid]≥nums[0],说明
m
i
d
mid
mid一定位于分界点
m
m
m左边,
[
l
e
f
t
,
m
i
d
]
[left, mid]
[left,mid]区间有序,再看
t
a
r
g
e
t
target
target是否位于其中,否则搜索另一半区间;若
n
u
m
s
[
m
i
d
]
<
n
u
m
s
[
0
]
nums[mid] < nums[0]
nums[mid]<nums[0],说明
m
i
d
mid
mid一定位于分界点右边,
[
m
i
d
,
r
i
g
h
t
]
[mid,right]
[mid,right]区间有序,再看
t
a
r
g
e
t
target
target是否位于其中,否则搜索另一半区间。
实现思路可参考LeetCode 153-Find Minimum in Rotated Sorted Array。
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
int ans = -1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
ans = mid;
break;
}
if(nums[mid] >= nums[0]){ // 同方法2,只需改动判断条件。满足这个条件说明mid位于分界点左边,[left,mid]这部分是有序的
if(target >= nums[left] && target < nums[mid]){
right = mid - 1;
}
else left = mid + 1;
}
else{ //mid位于分界点右边,[mid, right]这部分是有序的
if(target > nums[mid] && target <= nums[right]){
left = mid + 1;
}
else right = mid - 1;
}
}
return ans;
}