二分查找的本质,搜索旋转排序数组问题
整数数组 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 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-medium/xvyz1t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二分查找
刚拿到这道题,想到既然是旋转了一次的数组,那么肯定在旋转中心的两侧元素是局部有序的如:4567和012,要是我们能找到这个中心,然后在两侧进行二分搜索就好了。但其实如果寻找中心只能顺序查找,那么时间复杂度已经是O(n)了为什么不直接找target呢?
其实如果明白了二分查找的本质——根据有序元素段判断元素位置以不断收缩边界选择搜索空间,这道题也可以很轻松的想出来解法。比如,对于一个有序数组[1,2,3,4,5,6,7]。mid = 4,如果我们要查找元素6会很自然的去他的右端寻找[mid+1, left]。因为我们知道mid左侧的元素有序,故这些元素都比mid 4小,自然也就比6小了。
那么对于[4,5,6,7,0,1,2],可以想象,无论mid落到哪,在其两端都至少有一个局部有序段。假如mid为元素6,那么左侧456有序。若果mid为元素0,那么元素012有序。我们就可以利用有序段和当前target元素的比较,去排除一部分元素从而收缩边界继续寻找。只是我们的收缩边界部分有一些和原版二分查找不同地方。对于小于6的元素,因为只是局部有序,所以其左面有45,最右边也有012,故不能直接移动边界,还要判断一下元素是否在有序段内才可以。
代码:
public int search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
int mid = -1;
while(l <= r){
mid = l + (r - l) / 2;
if(target == nums[mid]){
return mid;
}
//mid至少会有一端有序,可以利用有序段收缩边界。
if(nums[mid] >= nums[l]){//左端有序
if(target < nums[mid] && target >= nums[l]){ //元素在左端
r = mid - 1;
}else{ //排除左端
l = mid + 1;
}
}else{//右端有序
if(target > nums[mid] && target <= nums[r]){ //元素在右端
l = mid + 1;
}else{ //排除右端
r = mid - 1;
}
}
}
return -1;
}
浙公网安备 33010602011771号