整数数组 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
。
有序数组,立即推!—>二分查找,本题就是以二分搜索为基础
我第一次看完题目是懵逼的,我就想这题目想表达什么,在数组里搜索有没有这个数?
后来才明白,实际上就是给你一个旋转后的数组,让你实现一个时间复杂度为(log n)级别的搜索算法,能够在旋转后的数组上用二分法查找目标元素,即如何在非有序的数组中使用二分查找?
本题给出的nums数组是经过旋转过后的数组,并不是有序的,旋转之后只能保证数组局部是有序的,但是不妨碍继续使用二分查找
因为进行旋转之后,根据旋转点将数组分开的时候,一定有一部分是有序的,如示例数组为 [4,5,6,7,0,1,2] ,如果拿6来当旋转点,则旋转之后的数组被分成了两部分,分别为[4,5,6],[7,0,1,2],其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此
即算法的核心思路为:
将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环…即不停的二分,二分完接着二分,一分为二后,有序的使用二分查找法,无序的继续二分
即在二分查找时,可以查看当前mid
(即数组的中间)为旋转点时,分割出来的两个部分[l,mid]
,[mid+1,r]
哪个部分是有序的,并且根据有序的那个部分继续去改变二分查找的上下界,因为可以通过有序的那部分判断target
在不在这部分中:
[l,mid-1]
是有序数组,即左边有序,且target
的大小满足[nums[l],nums[mid])
,左闭右开,则应该将搜索范围缩小至[l,mid-1]
,否则在[mid + 1, r]
中找[mid , r]
是有序数组,且target大小满足(nums[mid+1],nums[r]]
,则应该将搜索范围缩小至[mid + 1, r]
,否则在[l, mid - 1]
中寻找这里举个例子:
如[4,5,6,7,0,1,2]
,mid = 3
,即查看下标为3
的位置为分割点,[l,mid-1]
对应的数组值为[4,5,6]
,是有序的,以target = 5
为例,target = 5
满足这个范围,所以继续在这里面查找;以target = 2
为例,target = 2
不满足这个范围,即在[mid + 1, r]
为[0,1,2]
中寻找,此时已经是有序的了,可以直接用常规的二分
再如:[6,7,0,1,2,3,4,5]
,mid = 3
即查看下标为3
的位置为分割点,,[l,mid-1]
对应的数组值为[6,7,0]
,是无序的,所以看另一部分,[mid , r]
对应的数组值为[1,2,4,5]
,是有序的,以target = 4
为例,target = 4
在这个范围内,可以继续查找;以target = 6
为例,target = 6
不满足这个范围,所以在[l,mid-1]
里面找,即数组变成了[6,7,0]
,此时是无序的,继续找mid分成有序的和可能有序的两部分,继续二分…
java代码如下:
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
// 1. 首先明白,旋转数组后,从中间划分,一定有一边是有序的。
// 2. 由于一定有一边是有序的,所以根据有序的两个边界值来判断目标值在有序一边还是无序一边
// 3. 这题找目标值,遇到目标值即返回
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l ) / 2;//防止溢出
if (nums[mid] == target) {
return mid;//如果找到target的话在这里返回最终结果,否则返回-1
}
if (nums[l] <= nums[mid]) {//如果左边有序
if (target >= nums[l] && target < nums[mid]) {//且target的大小满足[nums[l],nums[mid]),左开右闭
r = mid - 1;//则应该将搜索范围缩小至[l,mid-1]
} else {
l = mid + 1;//否则在[mid + 1, r]中找
}
} else {//如果右边有序
if (target > nums[mid] && target <= nums[r]) {//且target大小满足(nums[mid+1],nums[r]]
l = mid + 1;//则应该将搜索范围缩小至[mid + 1, r]
} else {//否则在[l, mid - 1]中寻找
r = mid - 1;
}
}
}
return -1;
}
}