0x01.问题
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
)。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -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
public int search(int[] nums, int target)
0x02.分析思路
读题,发现是一个简单的搜索问题,但是要求时间复杂度为O(logN)
,一看这个搜索时间复杂度,就想到了二分法,可是二分法是对有序数组而言,这不是一个严格的有序数组,难道要先对数组排序嘛,排序的话,时间复杂度已经已超O(N)
了,肯定不行,所以,我们一定要利用好的条件。
题目的条件是一个有序数组旋转后得到的数组,虽然不是完全有序,但是,在以旋转点的左右两个部分,是完全有序的,如果我们可以利用这两部分来进行二分搜索,目的就达成了。
可是,问题是如何利用好这两部分有序部分。我们先来看一个例子。
- 原数组
[1,2,3,4,5,6,7,8]
,以5
开始进行旋转,得到数组[5,6,7,8,1,2,3,4]
。
我们观察这个数组有什么特殊没有,我们可以发现,原本升序的数组,在经过某个点旋转之后,较小的那部分会放到相对较后的位置,并且后面这部分有序的部分,一定满足<=nums[0]
,原本相对较大的部分,变成了前面的部分,并且这部分一定满足>=nums[n-1]
。
有了这个特征,我们就可以很快的判断出当前二分的点是在哪个部分:
- 如果当前二分的中点满足
>=nums[0]
,那么说明,这个中点是在左边有序部分,接下来就只需要判断目标点与nums[0],nums[mid]
的关系,即可在一定范围内搜索。 - 如果当前节点不满足
>=nums[0]
,说明这个点一定在右边的有序部分,此时,只需要判断目标点与nums[n-1],nums[mid]
的大小关系,并在一定范围内搜索。
综合上述两个步骤,我们发现,这样部分有序的数组,也是可以进行二分搜索的。
0x03.解决代码–二分搜索
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;
}
int left=0;
int right=n-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]==target){
return mid;
}
if(nums[mid]>=nums[0]){
if(nums[0]<=target&&target<nums[mid]){
right=mid-1;
}else{
left=mid+1;
}
}else{
if(nums[mid]<target&&target<=nums[n-1]){
left=mid+1;
}else{
right=mid-1;
}
}
}
return -1;
}
}
ATFWUS --Writing By 2020–04-27