当前位置: 首页 > news >正文

【LeetCode 热题 100】33. 搜索旋转排序数组——(解法二)一次二分

Problem: 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) 的算法解决此问题。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(log N)
    • 空间复杂度:O(1)

整体思路

这段代码同样旨在解决 “在旋转排序数组中搜索目标值” 的问题。与上一个“先找最小值,再二分”的两步法不同,此版本采用了一种更为一体化、也更具挑战性的 “单次二分查找” 策略。它通过一个精心设计的 check 函数,将复杂的判断逻辑融入到二分查找的每一步中,从而在一次循环内完成搜索。

算法的核心思想是:在二分查找的每一步,通过 nums[mid] 和数组边界值(nums[n-1])的关系,以及 target 和边界值的关系,来确定 targetnums[mid] 是否在同一个单调区间内。然后根据这个关系来决定是收缩左边界还是右边界。

  1. 染色法/划分思想

    • check(nums, mid, target) 函数是算法的灵魂。我们可以将其理解为一种“染色”或“划分”的逻辑。它将整个(概念上的)循环数组划分为两部分:一部分是 check 返回 true 的区域,另一部分是 check 返回 false 的区域。
    • 我们的二分查找目标就是找到这两个区域的分界线
  2. check 函数的逻辑详解

    • check 函数的目的是判断 nums[mid] 是否可能是我们要找的 target 或者在 target 的“右边”(在循环数组的意义上)。如果 check 返回 true,意味着最终答案在 mid 或其左侧;如果返回 false,则答案在 mid 的右侧。
    • check 内部的逻辑可以这样理解:
      a. x > nums[n-1]:这个条件判断 nums[mid] 是在数组的左半部分(蓝色区域)还是右半部分(红色区域)
      • Case 1: nums[mid] 在左半部分(蓝色)
        * 此时,如果 target 也落在这一段(即 target > nums[n-1]),并且 target <= nums[mid],那么 targetmid 在同一个单调区间内,且 targetmid 或其左侧。check 应返回 true
        * 如果 target 落在右半部分(即 target <= nums[n-1]),那么 targetmid 不在同一个单调区间内。target 相当于在 mid 的“右边”(循环意义上),check 应返回 false
        * return target <= x && target > nums[n-1] 这句代码巧妙地整合了这两种情况。
      • Case 2: nums[mid] 在右半部分(红色)
        * 此时,如果 target 落在左半部分(即 target > nums[n-1]),那么 target 相当于在 mid 的“左边”(循环意义上),check 应返回 true
        * 如果 target 也落在右半部分(即 target <= nums[n-1]),并且 target <= nums[mid],那么它们在同一个单调区间内,targetmid 或其左侧,check 应返回 true
        * return target > nums[n-1] || target <= x 这句代码也巧妙地整合了这两种情况。
  3. 二分查找框架

    • 代码使用了一个标准的 lower_bound 形式的二分查找框架 while (left < right)
    • 根据 check 函数的返回值来收缩边界:
      • check 返回 true,说明答案在 [left, mid] 区间,所以 right = mid
      • check 返回 false,说明答案在 [mid+1, right) 区间,所以 left = mid + 1
    • 循环结束后,left 指针会停在分界点上,也就是第一个可能等于 target 的位置。
    • 最后通过 nums[left] == target 确认并返回结果。

完整代码

class Solution {/*** 在一个旋转排序数组中搜索目标值(单次二分查找版)。* @param nums 旋转排序数组* @param target 要搜索的目标值* @return 目标值的索引,如果不存在则返回 -1*/public int search(int[] nums, int target) {int left = 0;int right = nums.length;if (nums.length == 0) return -1; // 边界检查,防止空数组// 这是一个 lower_bound 形式的二分查找框架while (left < right) {int mid = left + (right - left) / 2;// check 函数判断 mid 是否在 target 的“右侧”(或就是target)if (check(nums, mid, target)) {// 如果是,则答案在 [left, mid] 区间内right = mid;} else {// 如果不是,则答案在 [mid + 1, right) 区间内left = mid + 1;}}// 循环结束时,left 指向第一个可能等于 target 的位置。// 需要额外检查 left 是否越界以及该位置的值是否真的等于 target。return left < nums.length && nums[left] == target ? left : -1;}/*** 核心检查函数,用于在二分查找中确定收缩方向。* 它的逻辑是将整个循环数组根据 target 划分为两部分。* @param nums 数组* @param mid 中间点索引* @param target 目标值* @return 如果 mid 可能在答案的右半部分(包含答案本身),返回 true。*/private boolean check(int[] nums, int mid, int target) {int x = nums[mid];int n = nums.length;// Case 1: mid 落在数组的左侧有序部分 (e.g., [4,5,6,7,0,1,2], mid 在 4,5,6,7)if (x > nums[n - 1]) {// 要让 check 返回 true (即 right = mid),target 必须也在左侧部分,且 target <= x// 也就是 nums[n-1] < target <= xreturn target <= x && target > nums[n - 1];}// Case 2: mid 落在数组的右侧有序部分 (e.g., [4,5,6,7,0,1,2], mid 在 0,1,2)// 要让 check 返回 true (即 right = mid),target 的情况有两种:// a) target 落在左侧部分 (target > nums[n-1]),此时 mid 永远在 target 的右边(循环意义上)// b) target 也落在右侧部分,且 target <= xreturn target > nums[n - 1] || target <= x;}
}

时空复杂度

时间复杂度:O(log N)

  1. 二分查找:算法的核心是一个 while 循环,它实现了对长度为 N 的数组的二分查找。每次循环,搜索空间都减半。
  2. check 函数:在循环的每次迭代中调用的 check 函数,内部只包含几次比较和数组访问操作,其时间复杂度为 O(1)

综合分析
算法总共执行了 log N 次循环,每次循环的代价是 O(1)。因此,总的时间复杂度为 O(log N)

空间复杂度:O(1)

  1. 主要存储开销:算法没有创建任何与输入规模 N 成比例的新的数据结构。
  2. 辅助变量:只使用了 left, right, mid, x, n 等固定数量的整型变量。
  3. 递归深度:代码使用的是迭代而非递归,没有额外的栈空间开销。

综合分析
算法所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)

参考灵神

http://www.dtcms.com/a/305898.html

相关文章:

  • ragflow 报错ERROR: [Errno 111] Connection refused
  • 2025年6月电子学会青少年软件编程(C语言)等级考试试卷(三级)
  • LeetCode 面试经典 150_数组/字符串_轮转数组(6_189_C++_中等)(额外数组;转置)
  • 磁盘io查看命令iostat与网络连接查看命令netstat
  • Apache HTTP Server 2.4.50 路径穿越漏洞(CVE-2021-42013)
  • 矩阵指数函数 e^A
  • AR技术赋能航空维修:精度与效率的飞跃
  • 基于Catboost的铁路交通数据分析及列车延误预测系统的设计与实现【全国城市可选、欠采样技术】
  • Three.js 与 WebXR:初识 VR/AR 开发
  • nest generate从入门到实战
  • 6.Origin2021如何绘制Y轴截断图?
  • Java 笔记 封装(Encapsulation)
  • 常见存储卡类型及对比
  • Elasticsearch 索引管理 API 实战:涵盖创建、查看、修改、删除及别名
  • 10 分钟上手 Elasticsearch 语义搜索(Serverless Cloud 本地双版本教程)
  • 在 Kubernetes 上部署 Label Studio
  • 汽车免拆诊断案例 | 免拆诊断发动机起动困难故障2例
  • Visual Studio Code 使用指南 (2025年版)
  • lesson28:Python单例模式全解析:从基础实现到企业级最佳实践
  • 【网络工程师软考版】路由协议 + ACL
  • 网络资源模板--基于Android Studio 实现的图书商城App
  • 如何不让android studio自动换行
  • UDP通讯和TCP通讯的区别-UDP(用户数据报协议)和 TCP(传输控制协议)
  • 第十七章 追新词
  • Python 闭包(Closure) 特性剖析
  • Linux ps -ef 命令解析
  • 三十二、【Linux网站服务器】搭建httpd服务器演示虚拟主机配置、网页重定向功能
  • STM32学习记录--Day3
  • ubuntu编译opendds
  • STM32CubeMX 生成时钟获取函数的分析