搜索旋转排序数组(O(log n) 复杂度解法)

1. 题目描述

给定一个按 升序 排列的整数数组 nums,数组中的元素 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为:

复制代码
[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]

例如,数组 [0,1,2,4,5,6,7] 在下标 3 处旋转后可能变为 [4,5,6,7,0,1,2]

请实现一个高效算法, **O(log n)**时间复杂度内 找到目标值 target 在数组 nums 中的下标。如果 target 不存在,则返回 -1

示例

示例 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

2. 解题思路(两次二分查找)

第一步:找到数组的最小值索引(旋转点)

由于数组 nums 是一个 经过旋转的升序数组,它本质上可以划分为两个递增的子数组。例如:

复制代码
[4, 5, 6, 7, 0, 1, 2]

可以拆分成:

复制代码
第一段: [4, 5, 6, 7]
第二段: [0, 1, 2]

其中,0 是数组中的最小值,它的索引 i 就是旋转点。

如何找到 i

  • 由于 nums 旋转后仍然是两段单调递增数组,因此我们可以 利用二分查找 快速确定 i

  • 关键点在于:

    • 如果 nums[mid] < nums[n - 1],说明 mid 可能是最小值的右侧或等于最小值,因此移动 right = mid

    • 否则,mid 在旋转数组的左半部分,需要移动 left = mid

  • 这样,最终 right 指向的就是最小值的索引 i

第二步:使用二分查找目标值 target

找到 i 之后,nums 被分成两段 递增的子数组 ,我们可以使用二分查找快速定位 target

  • 如果 target > nums[n - 1]****,说明 target****在第一段 ,搜索范围是 (-1, i)

  • 否则, target****在第二段 ,搜索范围是 (i-1, n)

  • 在对应的区间内使用 二分查找 target****的下标


3. 代码实现

java 复制代码
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int i = findMin(nums); // 找到旋转点
        
        // 根据 target 的大小,判断在哪个子数组查找
        if (target > nums[n - 1]) {
            return lowerBound(nums, -1, i, target);
        }
        return lowerBound(nums, i - 1, n, target);
    }

    // 找到旋转点(数组的最小值索引)
    private int findMin(int[] nums) {
        int n = nums.length;
        int left = -1;
        int right = n - 1; // 搜索范围 (-1, n-1)
        
        while (left + 1 < right) {
            int mid = (left + right) >>> 1;  // 取中间索引,防止溢出
            if (nums[mid] < nums[n - 1]) {
                right = mid;  // 最小值在 [left, mid]
            } else {
                left = mid;  // 最小值在 [mid, right]
            }
        }
        return right;
    }

    // 在有序数组中查找 target 的最小索引
    private int lowerBound(int[] nums, int left, int right, int target) {
        while (left + 1 < right) {
            int mid = (left + right) >>> 1;  // 取中间索引
            if (nums[mid] >= target) {
                right = mid;  // target 可能在 [left, mid]
            } else {
                left = mid;   // target 可能在 [mid, right]
            }
        }
        return nums[right] == target ? right : -1;
    }
}

4. 复杂度分析

  • 寻找旋转点 的二分查找时间复杂度: O(log n)

  • 查找目标值 target 的二分查找时间复杂度: O(log n)

  • 总时间复杂度O(log n) + O(log n) = O(log n),符合题目要求。

  • 空间复杂度O(1),仅使用了常数额外空间。


5. 总结

  1. 旋转数组的特点

    • 两段递增数组 组成,第一段元素都大于第二段。

    • 通过二分查找可以快速找到最小值的索引,即旋转点。

  2. 两次二分查找

    • 第一次 :找到最小值索引 i,确定旋转点。

    • 第二次 :在正确的子数组中二分查找 target

  3. 时间复杂度 O(log n):两次二分查找保证了整体时间复杂度最优。

这种方法高效且易于理解,非常适用于 旋转排序数组的搜索问题。希望这篇文章能帮助你更好地理解该问题!🎯

相关推荐
闻缺陷则喜何志丹4 分钟前
【动态规划+前缀和+化环为链】P8810 [蓝桥杯 2022 国 C] 数组个数|普及+
c++·算法·前缀和·蓝桥杯·动态规划·洛谷·化环为链
tankeven5 分钟前
动态规划专题:00:线性动态规划:爬楼梯问题实例
c++·算法·动态规划
qq_334903158 分钟前
实时数据压缩库
开发语言·c++·算法
计算机安禾11 分钟前
【数据结构与算法】第1篇:为什么要学习数据结构与算法?专栏导学
c语言·开发语言·c++·学习·算法·visual studio code·visual studio
言之。11 分钟前
时间轮(Time Wheel)数据结构入门指南
开发语言·数据结构·python
月落归舟16 分钟前
帮你从算法的角度来认识链表------(一)
数据结构·链表·单链表
IronMurphy17 分钟前
【算法三十】124. 二叉树中的最大路径和
算法·深度优先
TechPioneer_lp20 分钟前
腾讯测试开发岗位 LeetCode 高频题汇总(2026版)
数据结构·算法·大厂笔试·leetcode高频题·腾讯测试开发·大厂校招·大厂春招
551只玄猫23 分钟前
【操作系统原理 实验报告6】磁盘调度算法
算法·操作系统·os·实验报告·操作系统原理·磁盘调度算法·磁盘调度
2301_7938046927 分钟前
C++中的访问者模式变体
开发语言·c++·算法