LeetCode 33:搜索旋转排序数组(二分查找进阶)

12 阅读3分钟

一、题目要求(LeetCode 33)

给定一个 可能经过旋转的升序数组 nums(数组中 不存在重复元素),以及一个目标值 target
要求在 O(log n) 时间复杂度内找到目标值的下标,若不存在则返回 -1

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [5,6,7,8,9,1,2,3], target = 9
输出:4

旋转排序数组的特点是:

  • 原数组是严格递增的
  • 在某个位置被“切断”并旋转
  • 整体不再完全有序,但 一定存在一段连续有序区间

二、解题思路:在旋转数组中做二分查找

普通二分查找依赖的是:

整个数组有序

而本题中数组只满足:

任意时刻,mid 的左边或右边 至少有一侧是有序的

核心思路可以总结为三步:

  1. 使用二分找到 mid

  2. 判断哪一侧是 有序区间

  3. 判断 target 是否落在该有序区间的 数值范围内

    • 在 → 缩小到这一半
    • 不在 → 去另一半继续查找

关键点:
不是看“mid 左右还有没有数”,而是看“target 是否在有序区间的值域中”


三、完整代码实现

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            // 找到目标
            if (nums[mid] == target) {
                return mid;
            }

            // 左半部分有序
            if (nums[left] <= nums[mid]) {
                // target 落在左半部分的值域内
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
            // 右半部分有序
            else {
                // target 落在右半部分的值域内
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }

        return -1;
    }
}

四、逐行逻辑解析

1. 初始化左右指针

int left = 0;
int right = n - 1;

维护一个标准的闭区间 [left, right]


2. 计算中点并判断是否命中

int mid = left + (right - left) / 2;

if (nums[mid] == target) {
    return mid;
}

这是最理想的情况,直接返回结果。


3. 判断哪一半是有序的

if (nums[left] <= nums[mid]) {

说明从 leftmid递增有序的

否则:

else {

说明右半部分 [mid, right] 是有序区间。


4. 左半部分有序时的处理

if (nums[left] <= target && target < nums[mid]) {
    right = mid - 1;
} else {
    left = mid + 1;
}

含义是:

  • 如果 target 在左半部分的 数值范围
  • 就可以直接丢弃右半部分
  • 否则目标一定在右侧

5. 右半部分有序时的处理

if (nums[mid] < target && target <= nums[right]) {
    left = mid + 1;
} else {
    right = mid - 1;
}

逻辑与左半部分完全对称,只是判断区间不同。


6. 未找到目标值

return -1;

循环结束仍未命中,说明数组中不存在该值。


五、关于常见疑惑的解释

很多人在理解这道题时会问:

如果 mid 落在左边,但 mid 的右边还有数,而且 target 正好在右边,怎么办?

答案是:

我们从来不是根据“左右有没有数”来判断的,而是根据“哪一半是有序的 + target 是否在该区间的值域中”。

只要 target 不在当前有序区间的数值范围内,就可以整段排除,绝不会漏解。


六、总结

  1. LeetCode 33 的本质是 二分查找的变形

  2. 旋转数组的关键特征是:局部有序

  3. 每一轮二分:

    • 一定存在一侧是有序的
  4. 判断方向时:

    • 不看下标
    • 只看 target 是否落在有序区间的数值范围
  5. 时间复杂度:O(log n)
    空间复杂度:O(1)

这道题是理解“二分查找进阶”的关键题,
真正吃透它,后面所有旋转数组、区间二分问题都会顺很多。