二分查找(搜索)| leetcode 33. 搜索旋转排序数组

本文介绍了如何解决LeetCode上的704题——搜索旋转排序数组。该问题要求在局部有序的旋转数组中寻找目标值,通过二分查找法,结合数组旋转特性,确定有序部分并更新查找区间。关键在于识别旋转点,然后根据目标值与旋转点的关系调整搜索范围。代码实现中,通过nums[mid]与nums[0]和nums[n-1]的比较来判断旋转点位置,并据此更新查找边界。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

33. 搜索旋转排序数组

二分查找(搜索)总结(一) | 边界控制

问题描述:

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= 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

提示:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

本题是 **704. 二分查找 的一种变体。

给定的数组虽然不是全局有序的,但依然满足局部有序的特点,因此我们可以在局部有序的部分进行二分搜索。

本题的关键点在于如何确定数组中局部有序的部分。

因为旋转数组是在有序数组的基础上旋转得来,可以把旋转数组看为两部分有序的数组组成;**因此把旋转数组从中间切分成两部分的话,一定有一部分是有序的。**我们可以对切分成的两部分进行判断,判断是左边部分有序还是右边部分有序,知道有序的部分后,我们就可以使用二分查找的方法对循环不变量进行更新,直至查找到指定元素。具体如下:

令初始值 left = 0right = n - 1,定义循环不变量[left, right] ,令 mid = (right - left) / 2;若 nums[mid] == target,则查找成功,返回结果;若 nums[mid] != target ,则需要确定下一轮的查找区间,以更新循环不变量

  • nums[mid] >= nums[0] 时,说明 [0, mid] 区间内的元素是有序的([mid, n-1] 内的元素是否有序,不清楚,可能有序,可能无序,要看数组的旋转策略);因此我们可以根据有序的部分来判断目标元素 target 是否在该区间内,从而决定循环不变量的更新策略。
    • nums[0] <= target && target < nums[mid] ,说明 target 在区间 [0, mid) 中,因此更新 right = mid - 1
    • target 不在有序区间 [0, mid) 中,则说明 target 落在区间 [mid + 1, right] 中,则更新 left = mid + 1
  • nums[mid] <= nums[n-1] ,分析同上。

实现代码:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0, right = n - 1;
        // 确定循环不变量,[left, right]
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            else if (nums[0] <= nums[mid]) {
                // [0, mid] 范围内的元素是有序的
                if (target >= nums[0] && target < nums[mid]) {
                    // nums[0] <= target < nums[mid]
                    right = mid - 1;
                }
                else {
                    left = mid + 1;
                }
            }
            else {
                if (target > nums[mid] && target <= nums[n-1]) {
                    left = mid + 1;
                }
                else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
};

几个细节思考:

  • 循环不变量 [left, right] 是动态更新的,可为什么判断是否有序是使用 nums[mid] 与 nums[0] 和 nums[n-1] 进行比较来判断?为什么不是与 nums[left] 和 nums[right] 进行比较判断?(与 ****81. 搜索旋转排序数组 II 的区别是什么?**)
  • 为什么是 nums[0] <= nums[mid]nums[mid] <= nums[n-1] ,而不是 nums[0] < nums[mid]nums[mid] < nums[n-1]
  • 为什么是 target >= nums[0]target <= nums[n-1] ,而不是 target > nums[0]target < nums[n-1]
  • 本题和 **81. 搜索旋转排序数组 II 题的差异点