题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 $O(\log n)$ 级别。
样例
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
算法1
(三次二分检索) $O(\log n)$
- 数组长度为
0
和1
时的特判处理。 - 首先二分出是以哪个元素分割数组两部分的。
- 具体为:每次二分时,如果
nums[mid] >= nums[l] && nums[mid] >= nums[r]
,则l = mid + 1
;如果nums[mid] <= nums[l] && nums[mid] <= nums[r]
,则r = mid
;否则break
。最终数组分为[0, l - 1]
和[l, n - 1]
两段区间。 - 然后再在两段区间分别二分找
target
即可。
时间复杂度
- 三次二分检索,时间复杂度是 $O(\log n)$。
空间复杂度
- 仅需要常数的额外空间。
C++ 代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
if (n == 0)
return -1;
if (n == 1)
return nums[0] == target ? 0 : -1;
int l = 0, r = n - 1, pivot;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] >= nums[l] && nums[mid] >= nums[r])
l = mid + 1;
else if (nums[mid] <= nums[l] && nums[mid] <= nums[r])
r = mid;
else break;
}
pivot = l - 1;
l = 0; r = pivot;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] < target)
l = mid + 1;
else
r = mid;
}
if (nums[l] == target)
return l;
l = pivot + 1; r = n - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] < target)
l = mid + 1;
else
r = mid;
}
if (nums[l] == target)
return l;
return -1;
}
};
算法2
(一次二分检索) $O(\log n)$
- 数组长度为
0
则直接返回-1
。 - 每一次二分,我们具体看一下什么时候答案可能在
[l, mid]
中,什么时候答案可能在[mid + 1, r]
中。 - 注意到
num[0]
是个非常关键的元素,已知数组被分为 两个升序部分,且后半部分全部比nums[0]
小,如果nums[i] >= nums[0]
,则说明[0, i]
一定是升序的,否则[i, n - 1]
一定是升序的。还可以根据target
与nums[0]
的关系,判断出target
可能属于哪一部分。 - 根据 3,如果给定了一个位置
mid
,我们可以根据nums[0]
、nums[mid]
与target
三者的关系,确定出target
可能属于哪一段区间。具体看代码注释部分。
时间复杂度
- 只有一次二分检索,时间复杂度为 $O(\log n)$。
空间复杂度
- 仅需要常数的额外空间。
C++ 代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
if (n == 0)
return -1;
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] >= nums[0]) { // mid 在数组前半部分。
if (target > nums[mid])
// 可以推出 target 的值一定大于 nums[0],target 只可能在 [mid + 1, r] 中。
l = mid + 1;
if (target < nums[0])
// 可以推出 target 的值一定小于 nums[mid],target只可能在 [mid + 1, r] 中。
l = mid + 1;
if (target <= nums[mid] && target >= nums[0])
// 此时 target 的值处于 nums[0] 和 nums[mid] 中,故可能在 [l, mid] 中。
r = mid;
} else { // mid 在数组后半部分
if (target >= nums[0])
// 可以推出 target 的值一定大于 nums[mid],target只可能在 [l, mid] 中。
r = mid;
if (target <= nums[mid])
// 可以推出 target 的值一定小于 nums[0],target只可能在 [l, mid] 中。
r = mid;
if (target > nums[mid] && target < nums[0])
// 此时 target 的值处于 nums[0] 和 nums[mid] 中,故可能在 [mid + 1, r] 中。
l = mid + 1;
}
}
return nums[l] == target ? l : -1;
}
};
请问一下大佬这个方法二中,if (nums[mid] >= nums[0]) 下的边界取法是不是有讲究,我稍微改变下就超出时间现在了
nums[0] 就是两段的分界
百度三面原题 高t面
我用的方法2 白板编程边界调出点小错误
面试官直接说我这个方法是错的 if (nums[mid] >= nums[0]) 不能说明任何问题?
我就笑了 他估计就会背一背LeetCode上面广为人知的方法一了
一点脑子不动 真tm恶心人
面试考二分的一律拉黑,没有任何意义
因为在紧张的情况下,很难准确的写清楚二分的边界,因此如果面二分,我觉得只需要把思路说清楚就行,代码有错就有错吧。
太赞了 是这样的 慌得不行 哈哈
老哥,你后面解决了这个边界小错误了吗
您好,对于 算法2 (一次二分检索) O(logn) 中 3 的描述:如果nums[i]>=nums[0]则说明[0, i]一定是升序的,否则[i+1, n-1]一定是升序的。后半句否则是否应该包括i呢?即 “否则[i, n-1]一定是升序的。”
已修正