📝个人主页:五敷有你
🔥系列专栏:算法分析与设计
⛺️稳中求进,晒太阳
题目
整数数组
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
思路
二分查找
数组本身不是有序的,进行旋转后只保证了数组的局部是有序的,这还能进行二分查找吗?答案是可以的。
可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。拿示例来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此。
这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:
在每一步循环中,计算当前中间元素的索引,并检查中间元素是否等于目标值。如果等于,则返回中间元素的索引。
如果中间元素不等于目标值,则根据中间元素与左边界元素的关系来判断当前部分是否为升序序列。
如果是升序序列,则判断目标值是否在该升序序列中,如果在,则将右边界移动到中间元素的左侧;如果不在,则将左边界移动到中间元素的右侧。
如果不是升序序列,即处于旋转点右侧,则判断目标值是否在右半部分的升序序列中。如果在,则将左边界移动到中间元素的右侧;如果不在,则将右边界移动到中间元素的左侧。
最终,如果找到目标值,则返回其索引;如果未找到,则返回 -1。
代码实现
class Solution {
public int search(int[] nums, int target) {
int right=nums.length-1;
int left=0;
while(left<=right){
int middle=(left+right)/2;
if(nums[middle]==target){
return middle;
}else if(nums[middle]>=nums[left]){
if(target>=nums[left]&&target<=nums[middle]){
right=middle-1;
}else{
left=middle+1;
}
}else{
//旋转点右边
if(target<=nums[right]&&target>=nums[middle]){
left=middle+1;
}else{
right=middle-1;
}
}
}
return -1;
}
}
运行结果
时间复杂度 O(logn)