33. Search in Rotated Sorted Array(搜索旋转排序数组)
1. 题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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
2. 二分查找(Binary Search) & 双指针(Two Pointers)
如果原数组没有被旋转,那我们可以直接使用二分法和双指针进行查找。只是现在数组被旋转了,二分法没法直接使用,而双指针的时间复杂度是O(n),不符合题意要求的O(log(n))。所以第一步我们需要考察一下升序数组被旋转之后,有几种情况:
- [0,1,2,4,5,6,7] -> [4,5,6,7,0,1,2] ,2和4中间为旋转轴;
- [1,3] -> [1,3],1左边为旋转轴或3右边为旋转轴;
- [1,3] -> [3,1],1和3中间为旋转轴,注意只有两个数的数组旋转后才会出现降序的效果,这个非常容易被忽略;
- [] -> []
以[4,5,6,7,0,1,2]这个为例,如果我们想用二分法来解题,我们可以把数组拆分成两个部分:1)[4,5,6,7];2)[0,1,2],分别进行二分查找即可。也就是我们需要找到数组中的峰顶或谷底,如下图:
只要找到峰顶或谷底,就可以分别对左右两边的数组进行二分查找,所以需要考察一下怎么找到峰顶或谷底,下面的方法统一找峰顶。我们先定义序号left = 0,right = 数组长度 - 1,mid = (right - left / 2) + left,通过观察我们可以发现:
- 如果数组是升序排序,(即nums[left] <= nums[mid] && nums[mid] <= nums[right]),只存在峰顶,那么可以直接使用二分查找,比如[1,2,3,4,5,6];
- 如果存在峰顶和谷底,那就不满足升序的特点,且nums[mid - 1] <= nums[mid] && nums[mid] > nums[mid + 1],那mid的位置是峰顶;如果nums[mid - 1] > nums[mid] && nums[mid] <= nums[mid + 1],则mid的位置是谷底;
- 如果存在峰顶和谷底,但是mid的位置处于"“山坡”上,即nums[mid - 1] < nums[mid] && nums[mid] < nums[mid + 1],这时我们需要考察一下mid与左右边界值的大小关系,来决定如何移动左右边界;比如[4,5,6,7,0,1,2],如果nums[mid] = 6,那nums[mid] > nums[left] = 4 && nums[mid] > nums[right] = 2,峰顶不可能在mid的左边,所以left = mid + 1,继续1. 查找;同理,如果nums[mid] = 1,峰顶不可能在mid的右边,所以right = mid - 1,继续1. 查找
- 如果是两个数,且是降序排序,应该满足nums[mid] <= nums[mid] && nums[mid] > nums[mid + 1],此时mid = 0,所以我们只需修改一下2. 的条件,变成:nums[mid - 1 >= 0 ? mid - 1 : 0] <= nums[mid] && nums[mid] > nums[mid + 1],即可覆盖到这个情况
到此找到了数组的峰顶,就可以直接使用二分法或双指针进行分段查找。
更新:本题代码可以进行简化,有兴趣的童鞋可以参考这里哒 - 81. Search in Rotated Sorted Array II(搜索旋转排序数组 II)两种解法(C++ & 注释)
3. 实例代码
class Solution {
// binarySearch 和 twoPointers二选一即可
int binarySearch(vector<int>& nums, int left, int right, int target) {
if (left == right) { if (nums[left] == target) return left; else return -1; }
else if (left > right) return -1;
int mid = (right - left) / 2 + left;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) return binarySearch(nums, mid + 1, right, target);
return binarySearch(nums, left, mid - 1, target);
}
int twoPointers(vector<int>& nums, int left, int right, int target) {
while (left <= right) {
if (nums[left] == target) return left;
if (nums[right] == target) return right;
left++;
right--;
}
return -1;
}
int findPeak(vector<int>& nums, int left, int right) {
int mid = (right - left) / 2 + left;
// 这个数组是升序排列
if (nums[left] <= nums[mid] && nums[mid] <= nums[right]) return -1;
// 找到峰顶和相邻的谷底
if (nums[mid - 1 >= 0 ? mid - 1 : 0] <= nums[mid] && nums[mid] > nums[mid + 1]) return mid;
if (nums[mid - 1] > nums[mid] && nums[mid] <= nums[mid + 1]) return mid - 1;
if (nums[left] <= nums[mid] && nums[mid] > nums[right]) return findPeak(nums, mid + 1, right);
return findPeak(nums, left, mid - 1);
}
public:
int search(vector<int>& nums, int target) {
if (!nums.size()) return -1;
int len = nums.size(), peak = findPeak(nums, 0, len - 1);
if (peak == -1) {
return twoPointers(nums, 0, len - 1, target);
//return binarySearch(nums, 0, len - 1, target);
}
else if (nums[0] <= target && target <= nums[peak]) {
return twoPointers(nums, 0, peak, target);
// return binarySearch(nums, 0, peak, target);
}
else if (nums[peak + 1] <= target && target <= nums[len - 1]) {
return twoPointers(nums, peak + 1, len - 1, target);
// return binarySearch(nums, peak + 1, len - 1, target);
}
return -1;
}
};