手摸手提桶跑路——LeetCode33.搜索旋转排序数组

603 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情

题目描述

整数数组 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

解题思路

切记,看到 有序、时间复杂度O(logn) 就要考虑这题能不能用二分来解。

首先,如果是正常的一个 有序数组,我们要查找某个数的下标,使用二分是没有问题的,通过不断的缩小搜索范围来逼近目标,大大减少了查询次数,提高了搜索效率。

但是这道题比较特殊,原本是有序的,好家伙给我在某个位置两极反转了,直接“啪”的一下,快乐消失了。

QQ图片20220810180756.png

不行,题还是得做的。

我们认真观察一下看看示例中的这个数组:[4,5,6,7,0,1,2],发现了什么?

说它无序吧,它还有点序,啥也不是。

难道有序的这边用二分,无序的这边用遍历啊?

我们可以发现在某个意义上,升序数组从某个节点旋转后的新数组,是可以看成 两个有序的升序数组 的,也就是两个数组各自 最右边的数大于最左边的数

我们首先判断 nums[mid] 是否大于 nums[left],如果成立,说明 [left, mid] 这部分是有序的,这时候我们就要想,target 是否在 [left, mid] 之间?也就是 target >= nums[left] && target < nums[mid] 是否成立。如果成立则说明 target 一定在 [left, mid] 之间,我们可以缩小范围,舍弃 [mid, right] 这部分的数据;如果不成立,则说明 target 应该是在 [mid, right] 这个数组中,我们应该舍弃 [left, mid] 这部分的数组。其实聪明的小伙伴这个时候就会发现,这边相对于正常的二分查找,就是 多了个分段的判断过程 而已。

题解

var search = function (nums, target) {
    let l = 0, r = nums.length - 1, mid;

    while (l <= r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] === target) return mid;
        if (nums[mid] >= nums[l]) { // 左边有序
            if (target >= nums[l] && target < nums[mid]) {
                r = mid - 1;
            } else {
                l = mid + 1
            }
        } else {
            if (target > nums[mid] && target <= nums[r]) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
    }
    return -1;
};

QQ截图20220816192734.png