33. 搜索旋转排序数组
难度 中等
整数数组 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
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums
中的每个值都 独一无二- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -10^4 <= target <= 10^4
题解
解法一
看到这道题的时候,首先想到的就是二分查找,但是和二分查找不同的是,这里的数组是升序数组的旋转。不能直接进行二分查找,然后相到,那就直接把旋转数组的分界找出来,然后在进行二分查找。
-
查找分界
int boundSearch(int[] nums){ int start = 0;//左指针 int end = nums.length - 1;//右指针 int mid = 0;//中指针 int pre = nums[0];//第一个值 while(start <= end){//结束条件 mid = start + ((end - start) / 2);//更新中间指针 if (nums[mid] == pre){//如果指针不移动了,就是升序的最大值 return mid; }else if(nums[mid] > pre){//如果中值大于第一个值,那么分界在右边 start = mid;//向右移动左指针 }else{ end = mid;//向左移动右指针 } pre = nums[mid];//每次都更新pre为第一个值 } return mid; }
如何进行查找分界呢,也利用二分的思想,查看一下中值是否大于第一个值;如果大于第一个值,那分界在中值右边,更新指针,再往右边找;如果小于第一个值,那么分界在中值左边,更新指针,再往左找。当发现中值等于第一个值,说明指针不移动了,此时指向的值就是最大值。
这里说明一下,为什么一定是最大值。
- 45670123,start = 0,end = 7,pre = nums[0] = 4,mid = (end + start) / 2 = 3,nums[mid] = 7 > pre,会走第二个条件,start = mid = 3,pre = nums[mid] = 7
- 45670123,start = 3,end = 7,mid = (end + start) / 2 = 5,nums[mid] = 1 < pre,会走第三个条件,end = mid = 5,pre = nums[mid] = 1
- 45670123,start = 3,end = 5,mid = (end + start) / 2 = 4,nums[mid] = 0 < pre,会走第三个条件,end = mid = 4,pre = nums[mid] = 0
- 45670123,start = 3,end = 4,mid = (end + start) / 2 = 3,nums[mid] = 7 > pre,会走第三个条件,start = mid = 3,pre = nums[mid] = 7
- 45670123,start = 3,end = 3,mid = (end + start) / 2 = 3,nums[mid] = 7 = pre,会走第一个条件,返回mid = 3
class Solution {
public int search(int[] nums, int target) {
int k = boundSearch(nums);//查找最大值边界
int ans1 = binSearch(nums, target, 0, k);//查找左边
int ans2 = binSearch(nums, target, k + 1, nums.length - 1);//查找右边
if(ans1 != -1){//左边找到
return ans1;
}else if(ans2 != -1){//右边找到
return ans2;
}
return -1;//没有找到
}
int boundSearch(int[] nums){
int start = 0;//左指针
int end = nums.length - 1;//右指针
int mid = 0;//中指针
int pre = nums[0];//第一个值
while(start <= end){//结束条件
mid = start + ((end - start) / 2);//更新中间指针
if (nums[mid] == pre){//如果指针不移动了,就是升序的最大值
return mid;
}else if(nums[mid] > pre){//如果中值大于第一个值,那么分界在右边
start = mid;//向右移动左指针
}else{
end = mid;//向左移动右指针
}
pre = nums[mid];//每次都更新pre为第一个值
}
return mid;
}
int binSearch(int[] nums, int target, int start, int end){//二分查找
while(start <= end){
int mid = start + ((end - start) / 2);
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
end = mid - 1;
}else{
start = mid + 1;
}
}
return -1;
}
}
解法二:官方题解
看到题解的的时候确实意想不到(反正每次都想不到的啦),官方题解还是利用二分到底,但是刚开始看文字解析的时候还不是很懂,看了几遍,才发现,原来是利用有序这个特点进行二分。
对整个数组进行二分的时候,出现三个点,nums[0],nums[mid],nums[nums.length - 1],由于数组有序,那么二分之后,一定在做边或者右边有序,因为只旋转了一次。那就出现两种情况,左边有序,右边有序
-
左边有序
左边有序,可以在左边二分,但是也要判断条件,要找的target在不在左边,否则则对右边查找
-
右边有序
同上,左边有序,可以在右边边二分,但是也要判断条件,要找的target在不在右边,否则则左边查找
-
有人就有疑问,为什么target不在有序的一遍,则去无需的一遍查找,但是无序的一边不满足二分查找的条件。其实模拟一下就好,当去到无需的一边以后,在根据上面判断左右边那边有序,又是重复的问题,就像分治一样,一直分治成非常小的问题,这里可以一直分治到有序,然后再二分。
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 l = 0;
int r = n -1;
while(l <= r){
int mid = (l + r) / 2;
if(nums[mid] == target){
return mid;
}
if(nums[0] <= nums[mid]){//左边有序
if(nums[0] <= target && target < nums[mid]){//在有序范围内,继续二分
r = mid - 1;
} else {//不再有序范围,移动左指针,去到无序范围查找
l = mid + 1;
}
} else {//右边有序
if(nums[mid] < target && target <= nums[n-1]){//在有序范围内,继续二分
l = mid + 1;
} else {//不再有序范围,移动右指针,去到无序范围查找
r = mid - 1;
}
}
}
return -1;
}
}