要求
给定一个没有重复元素的旋转数组(它对应的原数组是有序的),求给定元素在旋转数组内的下标(不存在的返回-1),要求算法时间复杂度越小越好。
函数定义:int Search(int[] nums, int target)
例子:
有序数组{0,1,2,3,4,5,6,7}对应的旋转数组为{3,4,5,6,7,0,1,2}(左旋、右旋效果相同)。
- 查找元素5,返回结果2;
- 查找元素8,返回结果-1。
分析:可以轻易地想到遍历一遍O(n)时间可以得到结果,但是并不是最好的结果;利用有序的特点,很容易就联想到要用二分查找的方法,从而将时间复杂度降到O(logn)
但是通过旋转后的数组是两个有序的数组拼接在一起,如何利用有序这个特点结合二分法从而达到O(logn)的时间复杂度?
如果我们取中间一个数来看,那么在这个数的两边,则必然有一边是有序的,而被这个中间数分割后的另一边则是无序的一边,如图所示:
定义每次循环二分查找的中间数下标为mid,最左边的数字下标为left,最右边的数字的下标为right,则该问题可以通过循环下述三个步骤解决
1)如果mid=target,则返回mid
2)如果num[mid]<num[right],则表示右边子序列有序,这时候如果nums[mid]<target<=num[right]则left=mid+1;否则right = mid-1
3)除上述两种两种情况,则只能是左边子序列有序,这时候如果nums[mid]>target >=num[left]则right = mid-1;否则left= mid+1
循环控制条件是left <=right
参考代码:
public static int search(int[] nums, int target){
int mid = nums.length/2;
ind left = 0;
int right = nums.length-1;
while(left <= right){
if(nums[mid] == target){
return mid;
}
// 右边有序,则看是修改right的值还是修改left的值
else if(nums[mid] < num[right]){
// target如果落在右边这个有序的子序列范围内,则修改left=mid+1
if(nums[mid] <target && target <= nums[right]){
left = mid+1;
}else{
right = mid -1;
}
}
// 左边有序,则看是修改right的值还是修改left的值
else{
if(nums[left] <= target && target < nums[mid]){
right = mid -1;
}else{
left = mid +1;
}
}
mid = (left+right)/2;
}
return -1;
}