【LeetCode 系列】LeetCode 33 搜索旋转排序数组
此题曾经出现在阿里和网易的一二轮面试中
题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
解题思路
因为题目要求时间复杂度为 O(log n) 级别,所以肯定是要用二分查找的。
现在我们来想一下,普通的二分查找是怎么做的?
在一个长度为 l 的有序数组 nums 中,找一个 target 元素。
- 将 start 记为 0,end 记为 nums.length,取数组中间的数,记为 nums[mid],mid 记为中间数的下标。
- 若 nums[mid] == target,则直接返回该元素。反之下一步。
- 若 nums[mid] < target,说明 target 在数组的右边,那么 start = mid + 1,end 不变(也就是在数组右半边找)
- 若 nums[mid] > target,说明 target 在数组的左边,那么 end = mid - 1,start 不变(也就是在数组左半边找)
- 重复第 2 步到第 4 步,没有匹配出 a[mid] == k 的条件,则返回 -1
现在情况是数组是旋转排序的,不是有序的。举个例子,1 2 3 4 5 6 7 若变成了旋转排序,可以大致分为两类。
- 3 4 5 6 7 1 2 这种,nums[start] <= nums[mid],有序的在左边。如果 nums[start] <= target < nums[mid],在左边找,否则去右边找。
- 5 6 7 1 2 3 4 这种,nums[start] > nums[mid],有序的在右边。如果 nums[mid] < target <= nums[end],在右边找,否则去左边找。
梳理本题思路
- 将 start 记为 0,end 记为 nums.length,将目标数记为 target。
- 取数组中间的数,记为 nums[mid],mid 记为中间数的下标。
- 若 nums[mid] == target 则直接返回该元素。反之下一步。
- 若 nums[start] <= nums[mid] 说明左边有序
4.1 若 nums[start] <= target < nums[mid],说明是 3 4 5 6 7 1 2 的情况,target 在左边,从左边开始找。
4.2 反之从右边开始找。 - 若 nums[start] > nums[mid] 说明右边有序
5.1 若 nums[start] 小于等于 num[mid],说明是 5 6 7 1 2 3 4 的情况,target 在右边,从右边开始找
5.2 反之从左边开始找。 - 一直重复 2 -> 5 步骤,直到 start > end。
本题 Accept 代码
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start <= end) {
// 计算中间下标 (防止溢出)
mid = start + (end - start) / 2;
// 若中间位置的数等于 target,立即返回下标
if (nums[mid] == target) {
return mid;
}
// 若 nums[start] 小于等于 num[mid],说明是 3 4 5 6 7 1 2 的情况,此时左边有序
if (nums[start] <= nums[mid]) {
// nums[start] <= target < nums[mid],说明 target 在左边,从左边找
if (target >= nums[start] && target < nums[mid]) {
end = mid - 1;
} else {
// 反之,从右边找
start = mid + 1;
}
} else {
// 若 nums[start] 小于等于 num[mid],说明是 5 6 7 1 2 3 4 的情况,此时右边有序
// nums[mid] < target <= nums[end],说明 target 在右边,从右边找
if (target > nums[mid] && target <= nums[end]) {
start = mid + 1;
} else {
// 反之,从左边找
end = mid - 1;
}
}
}
return -1;
}
}