news 2026/9/14 20:34:09

LeetCode hot100——33.搜索旋转排序数组:Java 二分模板与 O(log n) 实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode hot100——33.搜索旋转排序数组:Java 二分模板与 O(log n) 实现

一句话说明核心方法

旋转只“切一刀”,所以[left, mid][mid, right]至少有一段是升序的。每次循环先判断哪段有序,再看 target 是否落在那段里——是就在该段内二分,不是就丢弃该段转向另一段。整体仍是 O(log n) 二分。


思路推导

题意转化:在一个局部有序(旋转了一次)的数组里找 target 下标,仍要求 O(log n)。

关键观察 1:旋转只切一刀,所以永远有一半是“干净”的有序段。把原始升序[0,1,2,4,5,6,7]在 k=3 处旋转,得到[4,5,6,7,0,1,2]

切点(旋转点) ↓ 4 5 6 7 | 0 1 2 [有序递增段 ] [有序递增段]

两个段都还是升序,只是段间顺序“接错”了。在任意[left, right]子区间里取 mid,要么[left, mid]有序、要么[mid, right]有序(旋转点不可能同时落在两段内部)。这就是“在旋转数组上仍能二分的根本原因”。

关键观察 2:用nums[mid] >= nums[left]判哪段有序。左端点nums[left]是这一段的“地基”:

  • nums[mid] >= nums[left],说明[left, mid]这段没跨越切点,整段升序;
  • 否则说明切点在[left, mid]内部,[mid, right]才是干净的升序段。

注意用>=而非>——因为旋转点处nums[left]可能等于nums[mid](虽然本题约束无重复,但写法要养成左闭右闭区间下“等于归左”的习惯)。

**关键观察 3:在有序段里直接用 target 与两端点的关系做“跳转”。**比如判断出左半[left, mid]有序:

  • target ∈ [nums[left], nums[mid])→ target 在左半段内,下一轮往左搜(right = mid - 1);
  • 否则 target 要么在右半段里、要么根本不存在,下一轮直接跳到右半(left = mid + 1)。

对右半有序段做完全对称的判断。一句“target落点判断”就把“在这一段搜 / 丢掉这一段”的决策表达完了。

对比另一种思路:还有一种更直白的两步法——先二找出旋转点 k(数组最小值的下标),再判断 target 在[k, n-1]还是[0, k-1],最后在对应段做标准二分。代码会多一个找最小值的循环,但逻辑分支更少。一份循环直接定位vs三段式两步法,各有适用场景:前者面试展现“压缩分支”能力,后者更接近“从已知工具拼出来”的本能。本题里两种写法都是 O(log n)。


二分过程示意(nums = [4,5,6,7,0,1,2],target = 0)

初始: left = 0, right = 6 0 1 2 3 4 5 64 5 6 7 0 1 2 L M R mid = 3, nums[3] = 7 判断: nums[3]=7 >= nums[0]=4 → 左半 [0..3] 有序 [4,5,6,7] target=0 落在 [4,7)?4 ≤ 0 不成立 → target 不在左半 → left = 4 0 1 2 3 4 5 6 4 5 6 7 0 1 2 L M R mid = 5, nums[5] = 1 判断: nums[5]=1 >= nums[4]=0 → 左半 [4..5] 有序 [0,1] ←★关键:切点滑到 mid 左边了 target=0 落在 [0,1)? 0 ≤ 0 且0 < 1 成立 → 在左半 → right = 4 0 1 2 3 4 5 6 4 5 6 7 0 1 2 L,R mid = 4, nums[4] = 0 M nums[4] = 0 == target → return 4 ✓

注意第2 轮:mid=5 上的nums[5]=1 >= nums[4]=0仍然成立,于是左半[4,5] = [0,1]也被判为有序。只要 mid 不跨过切点,左半段的内部顺序就是对的——这正是“至少一段有序”不变量的现场演示。


Java 完整代码

java

class Solution { public int search(int[] nums, int target) { int left = 0; int right = nums.length - 1; // 左闭右闭 while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; // 命中(本题数组无重复,可提前返回) } // ① 判断哪半段是有序的 if (nums[mid] >= nums[left]) { // 左半 [left, mid] 有序 // ② target落在左半有序段里 →收左;否则切到右半 if (target >= nums[left] && target < nums[mid]) { right = mid - 1; } else { left = mid + 1; } } else { // 右半 [mid, right] 有序 // ③ 对称判断 if (target > nums[mid] && target <= nums[right]) { left = mid + 1; } else { right = mid - 1; } } } return -1; // 区间空,找不到 } }

关键代码逐行解释

  • if (nums[mid] == target) return mid——本题数组无重复(题目明确保证),命中即唯一,提前返回既正确又省轮次。如果迁移到 LeetCode 81(允许重复),就不能提前 return——重复元素下“== target” 会落在旋转点附近提前结束,留到结尾判定才稳。

  • nums[mid] >= nums[left]——左半有序的判定式。原理:左半[left, mid]整体升序,当且仅当它的两端nums[left] ≤ nums[mid],即nums[mid] >= nums[left]。等号成立的唯一情况是 mid == left(区间长度为 1),此时单元素区间天然有序,写>=把这种情况也正确归入“左半有序”。如果改用>,会出现 mid == left 时漏判、跳到 else 分支的隐患。

  • target >= nums[left] && target < nums[mid]——左半有序段的“落点判断”。注意右端用< nums[mid]而不是<=,因为nums[mid] == target已在前面 return,这里 mid 上的值一定不是 target,所以左半段的 target 范围是“含左端、不含右端”的左闭右开。反过来右半有序段用target > nums[mid] && target <= nums[right],“不含左、含右”。两段端点严格性相反,正是 mid 这个位置被排除两次的体现

  • else { left = mid + 1; }(左半有序但 target 不在内)——直接丢弃整个左半。target 要么在右半、要么不存在,下一轮往右搜。这步“丢半段”的力度和标准二分一样,复杂度仍是 O(log n) 的保证来源。

  • 右半有序的对称分支——target > nums[mid] && target <= nums[right]的语义镜像左半。两边边界严格性刻意相反(左半>= left && < mid、右半> mid && <= right),保证 mid 这个位置不归任何一边(因为它已经在前一行被排除了)。

  • return -1——区间被切到空(left > right)还没命中,说明数组里没有 target。这点其实在循环内已被覆盖(每轮必丢一半),但作为出口必须显式写,否则编译器报警告、判题也认。


时间、空间复杂度

  • 时间复杂度:O(log n)

    • 每轮循环至少把[left, right]区间严格缩短一半(要么left = mid + 1、要么right = mid - 1,mid 本身被排除),从 n 收敛到 1 最多 ⌈log₂ n⌉ 轮。本题数组无重复,最坏情况也严格 O(log n);如果迁移到含重复的 81,最坏退化为 O(n)(旋转点附近nums[mid] == nums[left]时无法判断哪半有序,必须线性收一格)。
  • 空间复杂度:O(1)

    • 只用 left / right / mid 三个变量,无递归无辅助结构。

易错点

  • 没判“哪半有序”直接套普通二分:把旋转数组当普通升序数组二分,target落在“切点后的降序段”时会被误丢,复杂度还可能错。判有序段是旋转二分的“前置动作”,省略就错

  • 有序段判定写成>而不是>=:mid == left 时(区间只剩两个元素)左半是单元素数组,本应有序,写nums[mid] > nums[left]会因为0 个元素差异或单元素相等被错归为“右半有序”,分支逻辑跑偏。

  • target 落点边界写错target <= nums[mid]在左半有序段里会出现 mid 上恰好等于 target 的情况,被前面nums[mid] == target排除后又错误归入“落在左半”,多绕一轮还可能正确,但严谨性扣分。左闭右开、右闭右开的严格性要和分支对齐

  • 没考虑 mid == left 的退化:n = 2 时 mid == left 是常态,判有序段 +落点要能正确处理单元素子区间,否则[3,1]target=1这种小输入会卡死或漏判。

  • 数组未旋转时还要走完整套逻辑:未旋转就是“切点在0”这种特殊情形,代码也应正常工作——你的写法天然兼容(nums[mid] >= nums[left]永远成立,整个区间判为左半有序,再走普通二分路径),不必另写分支。


可复用模板

抽出“先判有序段、再判落点”的母版,覆盖一切“部分有序数组”的搜索题:

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[mid] >= nums[left]) { // ① 左半有序 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; } }

变体提示

  • 找最小值 / 旋转点(LeetCode 153) → 把target换成“nums[mid]nums[right]的比较”,不查存在性,只维护“最小值在右半还是左半”的不变量。
  • 允许重复(LeetCode 81) →nums[mid] == nums[left]时无法判有序,把left++收缩一格,最坏退化为 O(n)。
  • 找旋转后数组的特定 target范围(如“找出 target 第一次出现和最后一次出现”) → 二分两遍,每遍用相同的“判有序段 + 判落点”逻辑,分支条件取target >= nums[mid]/target <= nums[mid]即可。

相似题及区别

  • LeetCode 81 搜索旋转排序数组 II:本题的“含重复”版。旋转点附近nums[mid] == nums[left]时无法判定哪半有序,需要left++收缩一格,最坏 O(n);本题因无重复,严格 O(log n)。这道题是检验“真懂二分 vs 只会套模板”的最佳陷阱题。

  • LeetCode 153 寻找旋转排序数组中的最小值:把“找 target”换成“找切点”,结构同源——同样判有序段(这次是nums[mid] > nums[right]),同样收半段,但不需要 target 落点判断。配合本题构成“旋转数组双子星”。

  • LeetCode 74 搜索二维矩阵:上一题。本题是“一维局部有序”,上一题是“二维全局有序(虚拟展开后)”,复杂度都是 O(log n) 但有序性的来源完全不同。本题是“旋转切一刀所以仍可二分”,上一题是“矩阵拼接成一条线所以天然二分”,对比阅读能加深对“何种有序才能二分”的理解。

  • LeetCode 34 在排序数组中查找元素的第一个和最后一个位置:把“找边界”的思路迁移过来——旋转数组里也能找 target 的最早 / 最晚位置,方法就是用本题模板跑两遍,分别把==归入左半或右半,正好对应 lower / upper bound 的等号方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 20:34:07

Natapp 内网穿透实战:无需公网服务器,远程桌面轻松访问内网电脑

Natapp 内网穿透实战&#xff1a;无需公网服务器&#xff0c;远程桌面轻松访问内网电脑 内网穿透并不缺方案。自己搭建 FRP 的自由度很高&#xff0c;但要先有一台带公网 IP 的服务器&#xff0c;再部署服务端、配置客户端、开放端口&#xff0c;后续还要处理升级、安全和日常…

作者头像 李华
网站建设 2026/9/14 20:32:05

AI大模型时代企业知识管理:构筑先进组织的核心数字竞争力

生成式AI技术快速普及&#xff0c;企业数据规模爆发增长&#xff0c;但多数组织同时陷入知识过载与知识孤岛双重困境。海量信息淹没真正有价值的业务经验&#xff1b;核心隐性知识伴随人员离职流失&#xff1b;各系统相互割裂&#xff0c;跨团队知识传递偏差大&#xff0c;大量…

作者头像 李华
网站建设 2026/9/14 20:31:56

MATLAB实现RRT路径规划算法详解

1. RRT算法基础与MATLAB环境准备快速扩展随机树&#xff08;Rapidly-exploring Random Tree, RRT&#xff09;是机器人路径规划领域的经典算法&#xff0c;特别适合解决高维空间中的复杂障碍规避问题。2001年由Steven M. LaValle首次提出时&#xff0c;主要针对机械臂的运动规划…

作者头像 李华
网站建设 2026/9/14 20:31:46

三维场景上帝视角实现指南:相机控制、数据组织与性能优化

老读者都知道&#xff0c;我这两年一直在折腾三维可视化相关的项目&#xff0c;从智慧园区到港口监控&#xff0c;从数字孪生大屏到无人机航线规划&#xff0c;前后做了七八个。这些项目有一个共性需求&#xff0c;客户不管前面聊得多么天花乱坠&#xff0c;最后验收的时候几乎…

作者头像 李华