【小白爬Leetcode33】6.2 搜索旋转排序数组 Search in Rotated Sorted Array
Leetcode33
m
e
d
i
u
m
\color{#FF4500}{medium}
medium
点击进入原题链接:Leetcode33 搜索旋转排序数组 Search in Rotated Sorted Array
题目
Discription
Suppose an array sorted in ascending order is rotated at some pivot
unknown to you beforehand.
(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).
You are given a target value to search. If found in the array return its index, otherwise return -1.
You may assume no duplicate exists in the array.
Your algorithm’s runtime complexity must be in the order of O(log n).
中文描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
思路 二分搜索的变种
设旋转点为pivot
。
这道题的难点在于发现一个规律:
虽然遭到旋转,但旋转点pivot
的前后都是升序的数组,进一步推断,在旋转后的数组中任选一个元素i
,i
的一边若是乱序的数组(包含pivot
的一变),那么另一边一定是升序的数组。那么,把i
具体化为mid
(数组中点)自然也不例外,这就为我们改良二分法提供了一个理论支持。
比较直观的一种实现:
分开讨论target<nums[mid]
和target>nums[mid]
的情况:
1.target<nums[mid]
首先讨论 target<nums[mid]
的情况,如果按照一般的二分搜索,肯定要在数组的前半段搜索,然后这里我们处理的可能是一个旋转数组,因为要讨论pivot
点落在哪一边。
if(nums[begin]<nums[mid])
,那么说明数组的前半段升序,那么pivot
点必落在后半段。此时:
若if(target>nums[begin])
,说明target
点落在前半段升序数组里,那么在前半段继续二分搜索;
若if(target==nums[begin])
,那这个begin
就是我们要找的结果;
若if(target<nums[begin])
,说明target
点落在了后半边的无序数组内,因此在后半段继续二分搜索。if(nums[begin]>nums[mid])
,那么说明数组的前半段无序,即pivot
点必落在前半段,那么后半段必然升序,也就是全部大于nums[mid]
,而target<nums[mid]
,那么搜索空间只能在前半段。if(nums[begin] == nums[mid])
,这是一种很特殊的情况,意味着数组里只有2个或1个数字,此时已知target<nums[mid]
,那么要想找到target
,数组只可能是[big,small]
这样的情况,即大的在前,小的在后,而target==small
。考虑到这种情况,令begin = mid+1
即可。- 如果经过二分搜索没有找到
target
,按照题意返回-1
即可。
2.target>nums[mid]
,这种情况和1是一样的道理;
3.target==nums[mid]
,和普通的二分搜索一样,这表示找到了target
元素,直接return mid
即可。
class Solution {
public:
int search(vector<int>& nums, int target) {
int begin = 0;
int end = nums.size()-1;
while(begin<=end){
int mid = (begin+end)/2;
if(target == nums[mid]) return mid;
if(target<nums[mid]){//目标值小于当前的中心值的情况
if(nums[begin]<nums[mid]){ //如果前半段是升序的
if(target>nums[begin]) end = mid-1;
else if(target==nums[begin]) return begin;
else //target<nums[begin]的情况,那么肯定在后半部分旋转部分
begin = mid+1;
}
else if(nums[begin]>nums[mid]){ //如果前半段无序
end = mid-1;
}
else//nums[begin]==nums[mid] 小于等于2个元素的特殊情况
begin = mid+1; //如果要能找到只可能是[7,1]这种前面比后面大的情况
}
if(target>nums[mid]){ //目标值小于当前的中心值的情况和上面基本类似
if(nums[begin]<nums[mid]){
begin = mid+1;
}
else if(nums[begin]>nums[mid]){
if(target<nums[end]) begin = mid+1;
else if(target==nums[end]) return end;
else end = mid-1;
}
else begin = mid+1;
}
}
return -1;
}
};
比较简洁的一种实现:
上面的方法将target<nums[mid]
和target>nums[mid]
分成了两种情况,其实二者可以合并为target!=nums[mid]
的情况,只要换一个角度想即可。
不妨把关注点放在数组的前半部分还是后半部分升序上:
- 若前半部分升序,那么考察target是否落在前半段,即
if (nums[0] <= target && target < nums[mid])
,若是则在前半段搜索,若不是则是后半段搜索; - 若后半部分升序,那么考察target是否落在后半段,即
if (nums[mid] < target && target <= nums[n - 1])
,若是则在后半段搜索,若不是则是前半段搜索;
下面放上我注释后的官方解答代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = (int)nums.size();
if (!n) return -1; //如果是空数组
if (n == 1) return nums[0] == target ? 0 : -1; //只有一个元素的数组直接判断就好
int l = 0, r = n - 1; //定义左右搜索端点
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) return mid;
if (nums[0] <= nums[mid]) { //如果是前半段升序
if (nums[0] <= target && target < nums[mid]) { //如果target在前半段之间
r = mid - 1; //应该在前半段搜索
} else { //否则应该在后半段
l = mid + 1;
}
} else { //如果是后半段升序
if (nums[mid] < target && target <= nums[n - 1]) { //如果target在后半段之间
l = mid + 1; //应该在后半段搜索
} else { //否则应该在前半段搜索
r = mid - 1;
}
}
}
return -1;
}
};