题目内容
整数数组 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
思路讲解
首先观察题目,一个不重复的升序数组,存在一个点打破了升序的条件,而且题目的核心是搜索一个符合target的数,再加上logn的复杂度,我们很容易想到二分查找。
但问题是二分查找的应用场景十分有限,仅仅可以在有序的数组中实现,这就暗示我们需要对经典二分进行一些改进。
我们先定义左右指针来确定具体查找边界,然后我们定位到数组的中间位置,暂时称这个指针为mid,此时数组可以被mid分为两部分。
聪明的你很快会观察到一个现象,以示例1的数组为例:nums = [4,5,6,7,0,1,2],这个数组很特殊,因为mid指向的数正好是数组的旋转点,那如果换到更一般的情况呢:nums = [4,5,6,7,8,9,0,1,2],此时数组被数字8分为左右两部分,左半部分有序,右半部分无序。
于是你脑洞大开,如果target在左半边有序数组的范围内不就可以运用二分了吗?但在这之前,你需要判断是左半边有序还是右半边有序,这组成了我们第一个if条件。
假如我们判断出左半边有序了,下一步就是确定target在左半边还是右半边,因为左半边是升序排列,如果target同时满足大于nums[left]且小于nums[mid]说明它在左半边,反之则在右半边,这构成了我们第二个if条件。
那么这个条件判断完后我们需要执行一些操作来缩小范围,还是接着上一步来,如果target在左半边的升序数组中,我们要把right指针指向mid-1的位置。
为什么?这正是二分查找的核心,借助mid指针来缩小范围,同时排除了右半部分的干扰,降低了复杂度,
那如果target在右半部分呢?右半部分可是无序的呀?那我们就把left指针指向mid+1的位置,本质还是缩小查找范围,尽管右半部分是无序的,但右半部分的无序数组又可以被新的mid指针分为有序和无序两部分,从而进一步缩小范围。
题目在这里大致思路就讲解完毕了,下面是代码实现。
代码实现
class Solution {
public int search(int[] nums, int target) {
if(nums.length==0)return -1;
if(nums.length==1&&nums[0]==target){
return 0;
}
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]==target)return mid;
if(nums[mid]>=nums[0]){
if(target<=nums[mid]&&target>=nums[left]){
right=mid-1;
}else{
left=mid+1;
}
}else{
if(target>=nums[mid]&&target<=nums[right]){
left=mid+1;
}else{
right=mid-1;
}
}
}
return -1;
}
}如果对你有帮助记得点赞支持!