思路
- 使用二分查找可以达到
O(logN)的时间复杂度。 - “旋转”的意思是按照顺序从nums的头部抽出几个元素然后按照顺序插到nums的尾部,因此旋转后的数组是两个升序子数组构成的。
- 用二分法一定能保证
[lo, mid][mid + 1, hi]这两个区间肯定有一个是升序的,另一个是升序/乱序的。 - 二分查找的本质:每次循环放弃一半的元素,即选择
mid左右两侧中的一侧,前提是必须保证整个数组是有序的,否则无法判断target和mid的大小关系来选择哪一边。
所以,这个题的关键在于搞清楚放弃其中一半的条件。
方法一:直接二分查找
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {//【情况A1,A2】
return mid;
} else if (nums[mid] >= nums[left]) {
if (target < nums[mid] && target >= nums[left]) {//满足这个条件,tar必在mid左侧的升序区间里【B1】
right= mid - 1;
} else {
left = mid + 1;//不满足if条件,表明tar必不在mid左侧的升序区间里,可以放弃左侧【C1】
}
} else {//如果后半部分有序
if (target > nums[mid] && target <= nums[right]) {//满足这个条件,tar必在mid右侧的升序区间里【B2】
left = mid + 1;
} else {
right = mid - 1;//不满足if条件,表明tar必不在mid右侧升序区间里,可以放弃右侧【C2】
}
}
}
return -1;
}
}
其中,
if (target < nums[mid] && target >= nums[lo])
要写target >= nums[lo],这样才可以把区间限制死,否则会出现这种情况:3456712中的12
方法二:先O(logN)找旋转点,再分别二分查找
最小点的前一个点一定是升序结束点
class Solution {
public int search(int[] nums, int target) {
int rp = findMin(nums) - 1;//rp为升序结束点的索引
int left = binarySearch(nums, target, 0, rp);
int right = binarySearch(nums, target, rp + 1, nums.length - 1);
if (left == -1 && right == - 1) {
return -1;
} else {
return Math.max(left, right);
}
}
public int findMin(int[] nums) {
int lo = 0;
int hi = nums.length - 1;
int min = Integer.MAX_VALUE;
int minIdx = 0;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] >= nums[lo]) {//左半部分升序
if (nums[lo] < min) {
min = nums[lo];
minIdx = lo;
}
lo = mid + 1;//搜索右半部分
} else {//右半部分升序
if (nums[mid] < min) {
min = nums[mid];
minIdx = mid;
}
hi = mid - 1;//搜索左半部分
}
}
return minIdx;
}
public int binarySearch(int[] nums, int target, int lo, int hi) {
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
hi = mid - 1;
} else if (nums[mid] < target) {
lo = mid + 1;
}
}
return -1;
}
}