力扣【二分查找】: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) 的算法解决此问题。
- 算法思路
题目要求查找指定数值且要求时间复杂度为logn,且数组升序排列不存在重复值经过旋转后得到,考虑使用二分查找
首先,由于数组经过旋转后被划分为两个分别有序的子数组,因此数组中必然存在一个idx使得 nums[idx] > nums[idx + 1],其中 0 < idx < size - 1
因此可以先遍历找到这个idx然后分别对两个子数组使用二分查找,但这样的时间复杂度在特殊情况下会达到O(n),因此不能用这种方法
相较于标准的二分查找问题,这里数组经过旋转后可能被分为两个分别升序排列的子数组,
在标准的二分查找中通过比较target和nums[mid]就可以不断逼近target,直到最后找到target或者发现数组中不存在target
观察数组发现,如果按照标准二分查找,如果mid落在left一侧,当target <= nums[mid]时target可能大于等于nums[left],也可能小于等于nums[right]
这说明我们无法简单地在这种情况下让right = mid,而是需要细化区分target所在的范围,如果target <= nums[right] 则就需要让left = mid + 1才能逼近target,
而当target > nums[mid]时,很显然可以让left = mid + 1,因为这样的区间只可能位于mid右侧,
同样如果mid落在right一侧,当target > nums[mid]时存在两种情况,要么target位于mid和right之间,这时可以让left = mid + 1,
要么target大于等于nums[left],此时right = mid才能逼近target
分析后发现,该题需要先判断mid位于left一侧或是right一侧,然后再根据target落入的区间决定left和right的变化,从而逼近target可能存在的位置
- 代码
时间复杂度:O(log n)
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
if (nums[left] <= target && target <= nums[mid]) {
right = mid;
} else {
left = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
}
if (nums[left] == target) {
return left;
}
return -1;
}
浙公网安备 33010602011771号