1. 题目描述
给定一个按 升序 排列的整数数组 nums
,数组中的元素 互不相同 。在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转,使数组变为:
[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
例如,数组 [0,1,2,4,5,6,7]
在下标 3
处旋转后可能变为 [4,5,6,7,0,1,2]
。
请实现一个高效算法,在 **O(log n)**时间复杂度内 找到目标值 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
2. 解题思路(两次二分查找)
第一步:找到数组的最小值索引(旋转点)
由于数组 nums
是一个 经过旋转的升序数组,它本质上可以划分为两个递增的子数组。例如:
[4, 5, 6, 7, 0, 1, 2]
可以拆分成:
第一段: [4, 5, 6, 7]
第二段: [0, 1, 2]
其中,0
是数组中的最小值,它的索引 i
就是旋转点。
如何找到 i
?
-
由于
nums
旋转后仍然是两段单调递增数组,因此我们可以 利用二分查找 快速确定i
。 -
关键点在于:
-
如果
nums[mid] < nums[n - 1]
,说明mid
可能是最小值的右侧或等于最小值,因此移动right = mid
。 -
否则,
mid
在旋转数组的左半部分,需要移动left = mid
。
-
-
这样,最终
right
指向的就是最小值的索引i
。
第二步:使用二分查找目标值 target
找到 i
之后,nums
被分成两段 递增的子数组 ,我们可以使用二分查找快速定位 target
:
-
如果 target > nums[n - 1]****,说明 target****在第一段 ,搜索范围是
(-1, i)
。 -
否则, target****在第二段 ,搜索范围是
(i-1, n)
。 -
在对应的区间内使用 二分查找 target****的下标。
3. 代码实现
java
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int i = findMin(nums); // 找到旋转点
// 根据 target 的大小,判断在哪个子数组查找
if (target > nums[n - 1]) {
return lowerBound(nums, -1, i, target);
}
return lowerBound(nums, i - 1, n, target);
}
// 找到旋转点(数组的最小值索引)
private int findMin(int[] nums) {
int n = nums.length;
int left = -1;
int right = n - 1; // 搜索范围 (-1, n-1)
while (left + 1 < right) {
int mid = (left + right) >>> 1; // 取中间索引,防止溢出
if (nums[mid] < nums[n - 1]) {
right = mid; // 最小值在 [left, mid]
} else {
left = mid; // 最小值在 [mid, right]
}
}
return right;
}
// 在有序数组中查找 target 的最小索引
private int lowerBound(int[] nums, int left, int right, int target) {
while (left + 1 < right) {
int mid = (left + right) >>> 1; // 取中间索引
if (nums[mid] >= target) {
right = mid; // target 可能在 [left, mid]
} else {
left = mid; // target 可能在 [mid, right]
}
}
return nums[right] == target ? right : -1;
}
}
4. 复杂度分析
-
寻找旋转点 的二分查找时间复杂度:
O(log n)
。 -
查找目标值 target 的二分查找时间复杂度:
O(log n)
。 -
总时间复杂度 :
O(log n) + O(log n) = O(log n)
,符合题目要求。 -
空间复杂度 :
O(1)
,仅使用了常数额外空间。
5. 总结
-
旋转数组的特点:
-
由 两段递增数组 组成,第一段元素都大于第二段。
-
通过二分查找可以快速找到最小值的索引,即旋转点。
-
-
两次二分查找:
-
第一次 :找到最小值索引
i
,确定旋转点。 -
第二次 :在正确的子数组中二分查找
target
。
-
-
时间复杂度 O(log n):两次二分查找保证了整体时间复杂度最优。
这种方法高效且易于理解,非常适用于 旋转排序数组的搜索问题。希望这篇文章能帮助你更好地理解该问题!🎯