整数数组 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)
的算法解决此问题
输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3 输出:-1
输入:nums = [1], target = 0 输出:-1
1 <= nums.length <= 5000
-104 <= nums[i] <= 104
nums
中的每个值都 独一无二nums
在预先未知的某个下标上进行了旋转-104 <= target <= 104
中等
搜索旋转排序数组
这道题我们仔细观察的话,其实本质还是一道二分查找的题目,只不过这个数组里面有两个二分查找的范围,我们需要确定应该在哪一个范围里去二分查找target。
我们不难发现,数组的第一个元素,是第一个二分查找范围的最小值,数组的最后一个元素是第二个二分查找范围的最大值,我们可以根据target和这两个元素的大小关系来确定在哪一个二分范围查找target。
如果target大于第二个二分查找的最大值又小于第一个二分查找的最小值,那个target一定不在这个数组里面,直接返回-1就可以了。
//目标值大于最右边的数,小于最左边的数,找不到
if(target > nums[nums.length - 1] && target < nums[0]){
return -1;
}
接着,我们可以用一个变量dir来标识我们应该查找哪一个范围,如果dir为0,就查找左边的范围,dir为1就查找右边的范围。通过判断target和第二个范围的最大值的关系来确定dir的值,如果target小于等于第二个范围的最大值,那么target就有可能在第二个范围中,dir设置为1;反之,我们就在第一个范围中查找,dir设置为0。
//0代表在最左边进行二分查找,1代表在最右边进行二分查找
//目标值小于等于最右边的数,在最右边进行二分查找
//目标值大于最左边的数,在最左边进行二分查找
int dir = target <= nums[nums.length - 1] ? 1 : 0;
然后,就是定义左右指针和二分指针进行二分查找了。二分查找的第一步是更新二分索引,接着判断二分索引指向的值是否等于target,是的话,直接返回。
int left = 0;
int right = nums.length - 1;
int mid = 0;
while(left <= right){
//更新中间值
mid = (right - left) / 2 + left;
//如果中间值target
if(nums[mid] == target){
return mid;
}
.......
}
如果不是的话,在正常的二分查找步骤之前,我们还要先判断我们是否在我们要二分查找的范围之中。判断方法很简单,我们根据前面的dir的值,如果dir等于0,说明我们要在左边的二分范围里面查找,这个时候如果我们的二分索引指向的值小于左边范围的最小值,那么我们还没有完全在左边的二分查找范围之内,右指针要移动,然后重新二分查找;如果dir等于1,说明我们要在右边的二分范围里面查找,这个时候如果我们的二分索引指向的值大于右边范围的最大值,那么我们还没有完全在右边的二分查找范围之内,左指针要移动。
while(left <= right){
......
//若还未进入二分查找的区间,则继续二分缩小范围
if(dir == 0 && nums[mid] < nums[0]){
right = mid - 1;
continue;
}else if(dir == 1 && nums[mid] > nums[nums.length - 1]){
left = mid + 1;
continue;
}
......
}
如果我们已经完全在我们确定的二分查找范围之内了,就按照正常的二分判断进行查找即可。
while(left <= right){
//更新中间值
mid = (right - left) / 2 + left;
//如果中间值target
if(nums[mid] == target){
return mid;
}
......
//正常的二分逻辑
if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
最后如果没有找到target,就直接返回-1。
class Solution {
public int search(int[] nums, int target) {
//目标值大于最右边的数,小于最左边的数,找不到
if(target > nums[nums.length - 1] && target < nums[0]){
return -1;
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
//0代表在最左边进行二分查找,1代表在最右边进行二分查找
//目标值小于等于最右边的数,在最右边进行二分查找
//目标值大于最左边的数,在最左边进行二分查找
int dir = target <= nums[nums.length - 1] ? 1 : 0;
while(left <= right){
//更新中间值
mid = (right - left) / 2 + left;
//如果中间值target
if(nums[mid] == target){
return mid;
}
//若还未进入二分查找的区间,则继续二分缩小范围
if(dir == 0 && nums[mid] < nums[0]){
right = mid - 1;
continue;
}else if(dir == 1 && nums[mid] > nums[nums.length - 1]){
left = mid + 1;
continue;
}
//正常的二分逻辑
if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
}
这道题是一道绕了一下的二分查找问题,有难度的地方就在于我们如果确定并找到target所在的二分查找范围,解决了这一点之后,就变成了一道简单的二分查找问题了。这道题就简单啰嗦到这里,祝大家刷题愉快~