(LeetCode-Hot100)33. 搜索旋转排序数组

问题简介

🔗 LeetCode 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. 初始化 left = 0, right = nums.length - 1
  2. left <= right
    • 计算中点 mid = (left + right) / 2
    • 如果 nums[mid] == target,直接返回 mid
    • 判断左半部分是否有序(即 nums[left] <= nums[mid]):
      • 如果是,则检查 target 是否在 [nums[left], nums[mid]) 范围内:
        • 若在,缩小右边界:right = mid - 1
        • 否则,搜索右半部分:left = mid + 1
      • 如果左半部分无序,则右半部分一定有序:
        • 检查 target 是否在 (nums[mid], nums[right]] 范围内:
          • 若在,left = mid + 1
          • 否则,right = mid - 1
  3. 循环结束仍未找到,返回 -1

💡 关键点:

  • 由于数组元素互不相同,nums[left] == nums[mid] 只有在 left == mid 时成立,此时仍可视为左半有序。
  • 每次都能排除一半的搜索空间,保证 O(log n) 时间复杂度。
❌ 方法二:线性扫描(不满足题目要求)
  • 直接遍历数组,时间复杂度 O(n),不符合 O(log n) 要求,仅作对比。
💡 方法三:先找旋转点再二分(可行但略复杂)
  1. 先用二分查找找到最小值(即旋转点)的位置 pivot
  2. 判断 target 应该在前半段还是后半段。
  3. 在对应段内进行标准二分查找。

虽然也是 O(log n),但需要两次二分,代码更复杂,不如方法一直接。


代码实现

Java Go

java 复制代码
复制代码
class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            
            if (nums[mid] == target) {
                return mid;
            }
            
            // 左半部分有序
            if (nums[left] <= nums[mid]) {
                if (target >= nums[left] && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } 
            // 右半部分有序
            else {
                if (target > nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        
        return -1;
}
go 复制代码
复制代码
func search(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right-left)/2
        
        if nums[mid] == target {
            return mid
        }
        
        // 左半部分有序
        if nums[left] <= nums[mid] {
            if target >= nums[left] && target < nums[mid] {
                right = mid - 1
            } else {
                left = mid + 1
            }
        } else {
            // 右半部分有序
            if target > nums[mid] && target <= nums[right] {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
    }
    return -1
}

示例演示

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

步骤 left right mid nums[mid] 有序部分 target 范围判断 新边界
1 0 6 3 7 左有序 0 ∉ [4,7) left=4
2 4 6 5 1 右有序 0 ∈ (1,2]? 否 right=4
3 4 4 4 0 --- 找到! return 4

✅ 成功找到目标值。


答案有效性证明

  • 正确性 :每次迭代都基于"至少有一半是有序的"这一性质,通过判断 target 是否落在有序区间内来决定搜索方向,逻辑严密。
  • 终止性 :每次循环要么找到目标,要么缩小搜索区间(leftright 移动),最终 left > right 退出。
  • 覆盖边界 :包括单元素、未旋转(k=0)、target 在旋转点等边界情况均能处理。

复杂度分析

项目 复杂度
✅ 时间复杂度 O(log n) ------ 每次排除一半元素
✅ 空间复杂度 O(1) ------ 仅使用常数额外空间

问题总结

📌 关键收获:

  • 旋转排序数组虽整体无序,但局部有序,可结合二分查找。
  • 判断哪一半有序是解题突破口。
  • 条件判断需严谨,注意边界(如 <= vs <)。

💡 扩展思考:

  • 若数组允许重复元素(如 LeetCode 81 题),则 nums[left] == nums[mid] 时无法判断哪边有序,需特殊处理(如 left++)。
  • 本题是"在部分有序结构中应用二分查找"的经典范例,对理解二分思想的灵活性很有帮助。

✅ 掌握此题,就掌握了处理"旋转数组"类问题的核心技巧!

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
你怎么知道我是队长1 小时前
计算机系统基础3---值的表示2---定点数与浮点数的介绍
算法
计算机毕设vx_bysj68691 小时前
计算机毕业设计必看必学~基于SpringBoot校园招聘系统的设计与实现,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!
java·spring boot·mysql·课程设计
云深处@2 小时前
【数据结构】栈
数据结构·算法
月明长歌2 小时前
Java 网络编程套接字入门:从“发一段数据”到“写一个可并发的服务器”
java·服务器·网络
没有bug.的程序员2 小时前
Git 高级进阶:分支管理模型内核、Rebase 物理重塑与版本控制协作深度实战指南
java·git·分支管理·版本控制·rebase
Anastasiozzzz2 小时前
深入理解JIT编译器:从基础到逃逸分析优化
java·开发语言·jvm
独自破碎E2 小时前
BISHI56 分解质因数
java·开发语言
啊我不会诶2 小时前
Codeforces Round 1076 (Div. 3) vp补题
算法·深度优先