题意简述:给定一个按升序排序的数组,这个数组进行了数次旋转(例如数组[0,1,2,4,5,6,7]在2和4之间执行一次旋转就会变成[4,5,6,7,0,1,2]。需要搜索某个数是否在这个数组里面。数组里面没有重复元素。
输入:数组nums,要搜索的值target
输出:如果target在nums里面,那么返回它在数组里面的位置下标;否则返回-1。
示例:对于nums=[4,5,6,7,0,1,2],target=5,应该返回5在nums中的下标1。
题解:
首先无论进行多少次旋转,数组最后只可能是两种状态:
- 跟原升序数组一致。
- 数组有一个断点,被划分为两段升序。
这是因为进行当前的旋转时,如果选择的断点跟上一次不一样,因上一次旋转而产生的两段升序又会连在一起变成一段升序。举个例子:对于数组[1,2,3,4,5],在2、3之间旋转会变成[3,4,5,1,2],断开的2和3在数组的首尾,下次在4、5之间旋转时,首尾的2和3又连在一起了([5,1,2,3,4])。如果选择的断点跟上一次一样,那就相当于撤销了上一次的旋转。
既然只有两种情况,那么我们只需加入分类讨论,就可以像排序数组一样使用二分搜索。存在断点的情况下,数更大的一段必定在数更小的一段的前面,所以考虑首尾两个值以及中间的值nums[low]、nums[up]、nums[mid](mid=(low+up)/2):
- 如果nums[low] < nums[mid],则数组的前半段就是升序的,直接在该段调用二分搜索;
- 如果nums[low] > nums[mid],则数组的前半段存在断点,无法直接调用二分搜索,因此递归调用函数在这半段继续以上的判断。
- 对数组的后半段的讨论跟以上两点类似。
- 只有在前半和后半都找不到target时才返回-1。
算法实现如下:
class Solution {
private:
int binarySearch(vector<int>& nums,int low, int up, int target) {
int mid, tempup = up;
low--;up++;
while(low + 1 != up) {
mid = low + (up - low) / 2;
if(nums[mid] < target) low = mid;
else up = mid;
}
if(up > tempup || nums[up] != target) return -1;
else return up;
}
int recur(vector<int>& nums,int low, int up, int target) {
if(low == up) {
if(nums[low] == target) return low;
else return -1;
}
int mid = low + (up - low) / 2;
int resl, resr;
if(nums[low] < nums[mid]) resl = binarySearch(nums, low, mid, target);
else resl = recur(nums, low, mid, target);
if(nums[up] > nums[mid]) resr = binarySearch(nums, mid+1, up, target);
else resr = recur(nums, mid+1, up, target);
if(resl != -1) return resl;
else return resr;
}
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0) return -1;
return recur(nums, 0, nums.size()-1, target);
}
};