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

问题简介

🔗 LeetCode 33. 搜索旋转排序数组

题目描述

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

解题思路

📌 核心观察:

虽然数组被旋转了,但它仍然由两个有序子数组 组成。我们可以利用二分查找的思想,在每次迭代中判断哪一半是有序的,并据此决定搜索方向。

✅ 方法一:改进的二分查找(推荐)

步骤如下:

  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 numsmid 有序部分 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 是否落在有序区间内来决定搜索方向,逻辑严密。
  • 终止性 :每次循环要么找到目标,要么缩小搜索区间(left 或 right 移动),最终 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

相关推荐
飞Link17 分钟前
什么是 PX4?一文了解 PX4 在空地协同中的作用
算法·无人机
一去不复返的通信er7 小时前
力扣-图论-岛屿数量
算法·leetcode·图论
考研保研资料分享8 小时前
985工科跨保上财应用统计:数学笔试、匿名面试与机试复盘
面试·职场和发展
wangchunyu1149 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
paopaokaka_luck9 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
Thomas.Sir9 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
程序 代码狂人10 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
HDU-Dade10 小时前
【校招笔试真题】简要介绍 Self-Attention 的前沿算法
人工智能·算法
2501_9445215910 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
Xin77011 小时前
LeetCode 79. 单词搜索(DFS 回溯法)
leetcode