【中等】 33. 搜索旋转排序数组

0 阅读3分钟

整数数组 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
  • -104 <= nums[i] <= 104
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -104 <= target <= 104

1. 生活案例:在两段断掉的电梯上找人

想象商场里有一部超长的自动扶梯,原本是从 1 楼直达 10 楼的。结果电梯断成了两截并错位了:

  • 上半段:6, 7, 8, 9, 10
  • 下半段:1, 2, 3, 4, 5
  • 现状:现在的顺序是 [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]

任务:老板让你找“3号乘客”在哪。

聪明的方法:你站在电梯中间(mid)。

  1. 你先看你脚下这一截电梯,哪一半是完整的(有序的)
  2. 如果你发现左边这截是完整的(比如从 6 到 10),你就看 3 号在不在 6 到 10 之间。
  3. 如果不在,那 3 号肯定在另一截断掉的电梯里。

2. 代码实现与详细注释

这是你图片中的代码,我为你加上了“找电梯”逻辑的详细中文注释:

JavaScript

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0;
    let right = nums.length - 1;

    while (left <= right) {
        let mid = Math.floor(left + (right - left) / 2);

        // 运气好,直接在中间找到了
        if (nums[mid] === target) return mid;

        // 【关键判断】:先确定哪一半是有序的
        if (nums[left] <= nums[mid]) {
            // 说明左半部分 [left...mid] 是连贯的、有序的
            // 就像电梯的 [6, 7, 8, 9, 10] 这一段
            if (nums[left] <= target && target < nums[mid]) {
                // 如果目标就在这段有序的范围内,那就把右边界拉回来
                right = mid - 1;
            } else {
                // 否则,目标肯定在右边那截断掉的电梯里
                left = mid + 1;
            }
        } else {
            // 说明右半部分 [mid...right] 是连贯有序的
            // 就像电梯的 [1, 2, 3, 4, 5] 这一段
            if (nums[mid] < target && target <= nums[right]) {
                // 如果目标在这段有序的范围内
                left = mid + 1;
            } else {
                // 否则,去左边那截找
                right = mid - 1;
            }
        }
    }

    // 找遍了也没找到
    return -1;
};

3. 核心原理解析

为什么这个逻辑行得通?

在旋转排序数组中,如果你从中间切一刀,一定至少有一半是有序的

  • 如果 nums[left] <= nums[mid],左边有序。
  • 如果 nums[left] > nums[mid],那么右边一定有序。

通过这个特性,我们可以利用有序的那一半来做范围判断。如果 target 不在有序的那一半,它就一定在无序的那一半。

复杂度分析

  • 时间复杂度O(logn)O(\log n)。虽然逻辑变复杂了,但本质还是每次砍掉一半的可能性,这就是二分查找的威力。
  • 空间复杂度O(1)O(1)。只用了几个指针变量。