33. 搜索旋转排序数组
问题描述:
整数数组 nums
按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7]
在下标 3
处经旋转后可能变为 [4,5,6,7,0,1,2]
。
给你 旋转后 的数组 nums
和一个整数 target
,如果 nums
中存在这个目标值 target
,则返回它的下标,否则返回 -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
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums
中的每个值都 独一无二- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -10^4 <= target <= 10^4
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
本题是 **704. 二分查找 的一种变体。
给定的数组虽然不是全局有序的,但依然满足局部有序的特点,因此我们可以在局部有序的部分进行二分搜索。
本题的关键点在于如何确定数组中局部有序的部分。
因为旋转数组是在有序数组的基础上旋转得来,可以把旋转数组看为两部分有序的数组组成;**因此把旋转数组从中间切分成两部分的话,一定有一部分是有序的。**我们可以对切分成的两部分进行判断,判断是左边部分有序还是右边部分有序,知道有序的部分后,我们就可以使用二分查找的方法对循环不变量进行更新,直至查找到指定元素。具体如下:
令初始值 left = 0
,right = n - 1
,定义循环不变量为 [left, right]
,令 mid = (right - left) / 2
;若 nums[mid] == target
,则查找成功,返回结果;若 nums[mid] != target
,则需要确定下一轮的查找区间,以更新循环不变量;
- 若
nums[mid] >= nums[0]
时,说明[0, mid]
区间内的元素是有序的([mid, n-1]
内的元素是否有序,不清楚,可能有序,可能无序,要看数组的旋转策略);因此我们可以根据有序的部分来判断目标元素 target 是否在该区间内,从而决定循环不变量的更新策略。- 当
nums[0] <= target && target < nums[mid]
,说明target
在区间[0, mid)
中,因此更新right = mid - 1
; - 若
target
不在有序区间[0, mid)
中,则说明target
落在区间[mid + 1, right]
中,则更新left = mid + 1
;
- 当
- 若
nums[mid] <= nums[n-1]
,分析同上。
实现代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int left = 0, right = n - 1;
// 确定循环不变量,[left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
else if (nums[0] <= nums[mid]) {
// [0, mid] 范围内的元素是有序的
if (target >= nums[0] && target < nums[mid]) {
// nums[0] <= target < nums[mid]
right = mid - 1;
}
else {
left = mid + 1;
}
}
else {
if (target > nums[mid] && target <= nums[n-1]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
}
return -1;
}
};
几个细节思考:
- 循环不变量 [left, right] 是动态更新的,可为什么判断是否有序是使用 nums[mid] 与 nums[0] 和 nums[n-1] 进行比较来判断?为什么不是与 nums[left] 和 nums[right] 进行比较判断?(与 ****81. 搜索旋转排序数组 II 的区别是什么?**)
- 为什么是
nums[0] <= nums[mid]
或nums[mid] <= nums[n-1]
,而不是nums[0] < nums[mid]
或nums[mid] < nums[n-1]
; - 为什么是
target >= nums[0]
和target <= nums[n-1]
,而不是target > nums[0]
和target < nums[n-1]
- 本题和 **81. 搜索旋转排序数组 II 题的差异点