33_搜索旋转排序数组[MEDIUM]
约 1260 字大约 4 分钟
2026-02-26
整数数组 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 。
你必须设计一个时间复杂度为 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示例 3:
输入:nums = [1], target = 0
输出:-1题目解析
题目给定了一个旋转排序数组 nums 和一个目标值 target。所谓的“旋转排序数组”,是指一个原本严格升序的数组,从中间某个未知的位置断开,把前半段平移到了数组的末尾(例如 [0,1,2,4,5,6,7] 变成了 [4,5,6,7,0,1,2])。数组中的元素都是唯一的。
我们需要在这个旋转过的数组中寻找目标值 target 的下标。找到返回下标,找不到返回 -1。题目明确要求算法的时间复杂度必须为 O(log n)。
解题思路
时间复杂度要求限制在了 O(log n),这说明必须要通过**二分查找(Binary Search)**来解决。
常规的二分查找要求数组完全有序,但这个数组在某个位置发生了旋转。不过,只要我们将这个数组从中间 mid 位置一分为二,这两个子数组中,一定至少有一个是完全有序的。
举个例子,对于数组 [4, 5, 6, 7, 0, 1, 2]:
- 若在
mid = 3(值为7)劈开,左边[4, 5, 6, 7]是完全有序的,右边[0, 1, 2]也是有序的。 - 若在
mid = 1(值为5)劈开,左边[4, 5]是完全有序的,右半边[6, 7, 0, 1, 2]包含了旋转点。 - 若在
mid = 5(值为1)劈开,右边[1, 2]是完全有序的,左半边[4, 5, 6, 7, 0]包含了旋转点。
利用“局部有序”的特性,我们可以对常规二分查找做如下改造:
- 找出中间元素
nums[mid],若正好与target相等,直接返回。 - 比较
nums[left]和nums[mid]的大小:- 如果
nums[left] <= nums[mid]:说明左半部分是完全有序的(严格递增)。- 此时,我们判断
target是否落在这个完全有序的左半区间内(即nums[left] <= target < nums[mid])。 - 如果落在,说明
target就在左半部分,我们将搜索区间缩小到[left, mid - 1]。 - 如果没落在,说明
target在右半部分,我们将搜索区间缩小到[mid + 1, right]。
- 此时,我们判断
- 否则(即
nums[left] > nums[mid]):说明右半部分是完全有序的。- 此时,我们判断
target是否落在这个完全有序的右半区间内(即nums[mid] < target <= nums[right])。 - 如果落在,说明
target就在右半部分,我们将搜索区间缩小到[mid + 1, right]。 - 如果没落在,说明
target在左半部分,我们将搜索区间缩小到[left, mid - 1]。
- 此时,我们判断
- 如果
- 重复以上步骤,直到
left > right,说明找不到,返回-1。
复杂度分析:
- 时间复杂度:O(logn)。每次迭代搜索范围都缩小一半,符合题目要求。
- 空间复杂度:O(1)。只需要常数级别的额外指针空间。
Java 实现
class Solution {
public int search(int[] nums, int target) {
// 如果数组为空或者长度为0,直接返回 -1
if (nums == null || nums.length == 0) {
return -1;
}
// 定义二分查找的左右边界
int left = 0;
int right = nums.length - 1;
// 当 left <= right 时,表示搜索区间仍有效
while (left <= right) {
// 计算中间索引,防止 left + right 导致的整型溢出
int mid = left + (right - left) / 2;
// 如果刚好找到目标值,直接返回
if (nums[mid] == target) {
return mid;
}
// 判断哪一部分是有序的
// 如果 left 到 mid 是有序的 (注意这里的 <= 是必须的,当 left == mid 时算作有序)
if (nums[left] <= nums[mid]) {
// 判断 target 是否落在这个有序的左半区间内
if (nums[left] <= target && target < nums[mid]) {
// 如果在,把搜索区间缩小到左半部分
right = mid - 1;
} else {
// 如果不在,只能去右半部分寻找
left = mid + 1;
}
}
// 否则,mid 到 right 必定是有序的
else {
// 判断 target 是否落在这个有序的右半区间内
if (nums[mid] < target && target <= nums[right]) {
// 如果在,把搜索区间缩小到右半部分
left = mid + 1;
} else {
// 如果不在,只能去左半部分寻找
right = mid - 1;
}
}
}
// 遍历完如果没有找到,返回 -1
return -1;
}
}