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
解法:二次二分
- 第一次二分:
根据与num[0]比较来划分,大于等于num[0]的即为数组的前半段,小于num[0]即为数组的后半段。
查找出旋转点 - 第二次二分:查找出target
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;
int mid;
while (l<r){
mid=l+r+1>>1;
if(nums[mid]>=nums[0]){
l=mid;
}else{
r=mid-1;
}
}
if(target>=nums[0]){
l=0;
}else {
l=l+1;
r=n-1;
}
while (l<r){
mid=l+r+1>>1; //l+r +1 +1是因为if判断中为<=,为了避免死循环而加。
if(nums[mid]<=target){
l=mid;
}else {
r=mid-1;
}
//下面是不+1 的写法
// mid=l+r>>1;
// if(nums[mid]<target){
// l=mid+1;
// }else {
// r=mid;
// }
}
return nums[r]==target? r:-1;
}
81. 搜索旋转排序数组 II
整数数组 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
此题是上面的加强版,在数组中会出现重复元素。并且可能出现第一次二分的干扰项,例子如下
2 3 4 5 1 2 2
n=nums.length
l=0
r=n-1
此时num[r]==num[0],即在后半段的数也存在大于等于num[0]的数,所以此时我们需要恢复二段性。所以当
l<r(确保数组不会越界 越界例子 1,1,1,1,1,1)且num[r]==num[0]时,r--。
下附具体代码
public boolean search(int[] nums, int target) {
int n=nums.length;
if(n==0)return false;
if(n==1) return nums[0]== target? true:false;
int l=0;
int r=n-1;
int mid;
//恢复二段性
while(l < r && nums[0]==nums[r]) r--;
//第一次二分 ,查找旋转点
while (l<r){
mid=l+r+1>>1;
if(nums[mid]>=nums[0]){
l=mid;
}else{
r=mid-1;
}
}
int idx=n;
if(r+1<n) idx=r+1;
//判断target 在前半段还是后半段
if(target>=nums[0]){
l=0;
r=idx-1;
}else {
l=idx;
r=n-1;
}
//第二次二分 ,查找target
while (l<r){
mid=l+r+1>>1; //l+r +1 +1是因为if判断中为<=,为了避免死循环而加。
if(nums[mid]<=target){
l=mid;
}else {
r=mid-1;
}
}
return nums[r]==target? true:false;
}
总结
- 有序队列查值,用二分。
- 「二分」的本质是两段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。
此文章创于本人学习时的记录,如有错误或更优解还请指出