33. 搜索旋转排序数组 + 二分法 + 旋转数组
题目来源
相似题目
33. 搜索旋转排序数组
153. 寻找旋转排序数组中的最小值
154. 寻找旋转排序数组中的最小值 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
进阶: 你可以设计一个时间复杂度为 O(log n)
的解决方案吗?
题解分析
解法一
- 题目实际考察的是二分法。
- 虽然旋转后的数组是部分有序的,但是由于每次迭代都必然有一部分(左部分或者右部分)是有序的,此时可以判断当前数是否在有序的那部分,进而控制上下界。
package com.walegarrett.interview;
/**
* @Author WaleGarrett
* @Date 2021/2/28 19:21
*/
/**
* 题目描述:整数数组 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 。
*/
public class LeetCode_33 {
public int search(int[] nums, int target) {
int len = nums.length;
int low=0, high = len-1;
while(low <= high){
int mid = (low+high)>>1;
if(nums[mid] == target)
return mid;
if(nums[0] <= nums[mid]){//前半部分有序
if(nums[mid] > target && nums[0] <= target)
high = mid-1;
else low = mid + 1;
}else{//后半部分有序
if(nums[mid] < target && nums[len-1] >= target)
low = mid + 1;
else high = mid - 1;
}
}
return -1;
}
}
解法二
- 本题与传统二分的最大区别就是这里的数组是分段有序的,总共就有两种分段,而mid落在哪个分段是不确定的。
- 我们可以分情况考虑,因为每次求解出的mid和区间的分布总共就三种情况,如下:
左、中、右三个位置的值相比较,有以下几种情况:-
左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界
右 中 左
-
左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界
左 右 中
-
左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界
中 左 右
-
左值 > 中值, 中值 > 右值 :单调递减,不可能出现
左 中 右
-
- 我们可以首先根据以上的情况,分成两种情况来考虑,接着判断mid是在哪个区间,即左区间或者右区间,然后进行区间的缩小。
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n-1;
while(left <= right){
// 最多存在三种情况:
// 右
// 中
// 左
// 左
// 右
// 中
// 中
// 左
// 右
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] >= nums[left]){// mid在左半部分
if(target >= nums[left] && target <= nums[mid]){
right = mid - 1;
}else{
left = mid + 1;
}
}else if(nums[mid] < nums[left]){// mid在右半部分
if(target >= nums[mid] && target <= nums[right]){
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return -1;
}
}
Either Excellent or Rusty