搜索旋转排序数组?二分查找直接搞定!

134 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情


大家好呀,我是旋转蛋。

今天解决搜索旋转排序数组,用二分查找解决局部有序数组的经典问题。

话不多说,让我们来会一会它。

33-0

LeetCode 33:搜索旋转排序数组

题意

整数数组 nums 升序排列且无重复元素。

给你从某个下标旋转后的数组 nums 和 target,如果 target 在 nums 中返回下标,否则返回 -1。

示例

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

输出:4

解释:原 nums = [0,1,2,4,5,6,7],从下标 3 旋转后变为现在的 nums = [4,5,6,7,0,1,2]

提示

数据保证 nums 在预先未知的某个下标上进行了旋转,nums 中的每个值独一无二。

  • 1 <= len(nums) <= 5000
  • -10^4 <= nums[i]、target <= 10^4

题目解析

又是一种新的题型,难度中等。

我们早就知道,二分查找必须的条件是数组有序。

刚看这题题意第一行的时候,整数数组 nums 升序排列且无重复元素,当时我就默默的掏出了我的二分查找板子。

辣鸡,写的这么明显,当我是傻子嘛。

33-1

然后还没开心到高潮,发现 nums 被人扭了一下,旋转了。

小丑竟是我自己...

出题人你出来,你当数组是尼玛魔方嘛,想扭就扭。

33-2

不过再定睛一看,这题二分查找还是可解。

因为数组被旋转以后,总有一部分是有序的

比如 [4,5,6,7,0,1,2] 中,我找到 mid,不管是 0 ~ mid 或者 mid ~ n-1,总有半拉子是有序的,我们在有序的那边进行二分查找是可以的。

那这样二分查找稍微变一下,加个有序的判断,这道题的解法就出来了:

  • 找出 mid,如果 nums[mid] == target,直接返回。
  • 如果 [low,mid-1] 有序:
    • target 在 [nums[low],nums[mid]] 中,范围缩小至 [low,mid-1]。
    • target 不在 [nums[low],nums[mid]] 中,范围缩小至 [mid+1,high]。
  • 如果 [mid+1,high] 有序:
    • target 在 [nums[mid+1],nums[high]] 中,范围缩小至 [mid+1,high]。
    • target 不在 [nums[mid+1],nums[high]] 中,范围缩小至 [low,mid-1]。

下面我们来看图解。

图解

以 nums = [4,5,6,7,0,1,2], target = 0 为例。

首先初始化 low 和 high 指针。

33-3

low, high = 0, len(nums) - 1

第 1 步,low = 0,high = 6,mid = low + (high - low) // 2 = 3:

33-4

mid = low + (high - low) // 2

此时 nums[low] < nums[mid],且 target 不在左区间内,所以 low 向右移动至 mid + 1 = 4。

33-5

# 如果左区间有序
if nums[low] <= nums[mid]:
    # target 在左区间
    if nums[low] <= target < nums[mid]:
        high = mid - 1
    # target 在右区间
    else:
        low = mid + 1

第 2 步,low = 4,high = 6,mid = low + (high - low) // 2 = 5:

33-6

此时 nums[low] < nums[mid],且 target 在左区间内,所以 high 向左移动至 mid - 1 = 4。

33-7

第 3 步,low = 4,high = 4,mid = low + (high - low) // 2 = 4:

33-8

此时 nums[mid] == target ,直接返回结果。

# 如果找到,返回结果
if nums[mid] == target:
    return mid

本题解使用二分查找,时间复杂度为 O(n)

同时只额外维护了几个指针,所以空间复杂度为 O(1)

代码实现

Python 代码实现

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
​
        low, high = 0, len(nums) - 1
​
        while low <= high:
            mid = low + (high - low) // 2
            # 如果找到,返回结果
            if nums[mid] == target:
                return mid
            # 如果左区间有序
            if nums[low] <= nums[mid]:
                # target 在左区间
                if nums[low] <= target < nums[mid]:
                    high = mid - 1
                # target 在右区间
                else:
                    low = mid + 1
            # 如果右区间有序
            else:
                # target 在右区间
                if nums[mid] < target <= nums[high]:
                    low = mid + 1
                # target 在左区间
                else:
                    high = mid - 1
​
        return -1

Java 代码实现

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n == 0) {
            return -1;
        }
        if (n == 1) {
            return nums[0] == target ? 0 : -1;
        }
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[0] <= nums[mid]) {
                if (nums[0] <= target && target < nums[mid]) {
                    r = mid - 1;
                } 
                else {
                    l = mid + 1;
                }
            } 
            else {
                if (nums[mid] < target && target <= nums[n - 1]) {
                    l = mid + 1;
                } 
                else {
                    r = mid - 1;
                }
            }
        }
        return -1;
    }
}

图解搜索旋转排序数组到这就结束辣,又是一种新的题型,你学废了嘛?

33-9

还是那句话,二分查找,只要每次做题小心点,就没啥问题。