题目描述
思路解析动画文字版
挨个找当然行,可那就白白浪费了「旋转前本来有序」这个条件。题目都把有序明牌摆你面前了,必须把二分用起来。
转折:旋转数组虽整体无序,但从 mid 切两半,总有一半是完整升序(端点一比就知道:nums[l] ≤ nums[mid] 则左半有序,否则就是右半有序)。认出有序半后,只要看 target 在不在那半的范围里就能决定丢哪边——照样每步砍一半。
准备 · 闭区间 [0,6]:搜索范围是整个数组,l=0、r=6。要找的 target = 0。
第 1 轮 · 取 mid:中点 mid=3 处是 7,不等于目标 0。先判断哪半有序。
第 1 轮 · 判有序半:比端点:nums[l]=4 小于等于 nums[mid]=7,说明左半 [4,5,6,7] 是有序的(高亮那段)。那就拿 target 和这个有序半的范围比。
第 1 轮 · target 不在有序半 → 去右半:0 不落在有序左半 [4,7)(含左端 4、不含已单独判过的 mid=7)里,所以它必在另一半。整个左半丢掉(灰),l 跳到 4。
第 2 轮 · 取 mid:只看右段 [0,1,2]。新中点 mid=5 处是 1,仍不等于 0。
第 2 轮 · 判有序半:同样先比端点:nums[4]=0 小于等于 nums[5]=1,左半 [0,1] 有序。拿 target 和它的范围比。
第 2 轮 · target 在有序半 → 收右:这次 0 正好落在有序左半 [0,1)(含左端 0、不含已单独判过的 mid=1)里,所以去这半找:丢掉 mid 右边,r 收到 4。
第 3 轮 · 命中!:区间只剩一个,mid=4 处正好是 0 = target。返回下标 4。每轮都砍掉一半,所以是 O(log n)。
注意:上面三轮 mid 都落在左半,比出来都是「左半有序」。可代码里还有个 else——当 nums[l] > nums[mid] 时,是右半有序。换个例子把这条分支也跑一遍,套路才算闭环。
换例 · nums=[6,7,0,1,2,4,5],找 5:换一个旋转得更狠的数组 [6,7,0,1,2,4,5],要找的 5 在靠右那段。看 mid 落到小数那边会发生什么。
第 1 轮 · 取 mid:中点 mid=3 处是 1,不等于 5。照例先判哪半有序。
第 1 轮 · 判有序半 → 这次是右半!:关键差别来了:nums[l]=6 大于 nums[mid]=1,左半不再是升序——说明断点在左边,反而是右半 [1,2,4,5] 完整有序(高亮右段)。这就是代码里的 else 分支。
第 1 轮 · target 在右有序半 → 去右半:这回比的是右有序半的范围:5 落在 (1, 5] 里,所以去右半。丢掉 mid 左边(灰),l 跳到 4。注意这里收的是 l,正好和左半有序那支相反。
第 2 轮 · 取 mid:剩下 [2,4,5]。新中点 mid=5 处是 4,还不是 5。
第 2 轮 · 判有序半:这小段又恢复成左半有序:nums[4]=2 小于等于 nums[5]=4。5 不在 [2,4] 里,所以去另一半,l 跳到 6。
第 2 轮 · 命中!:区间只剩 mid=6,正好是 5 = target。返回下标 6。两条分支(左半有序 / 右半有序)现在都跑通了。
旋转数组的二分,核心就这一句:哪半有序看得出来(端点比一下),target 在不在那半也看得出来(范围比一下)。剩下交给二分。
迁移阶梯:把这题练到能复述后,再去同类题里迁移:先复用「定有序半」的状态判断,再调整边界条件。也可以直接问问右侧的小欧:「LC81 有重复元素时这套二分会怎么退化?」
边界三连:边界三连:极端输入先过一遍。
几个高频追问,记住答法。
参考代码
class Solution: def search(self, nums, target): l, r = 0, len(nums) - 1 while l <= r: mid = (l + r) // 2 if nums[mid] == target: return mid if nums[l] <= nums[mid]: # 左半有序 if nums[l] <= target < nums[mid]: r = mid - 1 else: l = mid + 1 else: # 右半有序 if nums[mid] < target <= nums[r]: l = mid + 1 else: r = mid - 1 return -1复杂度
- 时间复杂度:O(log n),每轮砍一半
- 空间复杂度:O(1),几个指针
可套用的代码模板
这是挖空骨架,不是直接抄解。记牢三句:①先比 nums[l] 和 nums[mid] 定有序半;②if 这支管左半有序、else 这支管右半有序(两支都要写);③用「有序半的范围」判 target 落哪半——左半有序时是 [nums[l], nums[mid])(含左端、不含 mid,mid 已先判过命中),右半有序时是 (nums[mid], nums[r]](不含 mid、含右端)。把注释处的比较填进去就是完整解。LC81(含重复)、LC153(找最小)都是它的变体。
# 旋转二分骨架:l<=r闭区间搜索 + 先定有序半 + 半开范围判落点while l <= r: mid = (l + r) // 2 if nums[mid] == ___目标命中: return mid # 先判命中 if nums[l] <= nums[mid]: # 端点比一下:左半有序 if target 在 [nums[l], nums[mid]): 收右 r = mid-1 # 含左端,不含mid else: 否则去另一半 l = mid+1 else: # 否则右半有序(别忘这支!) if target 在 (nums[mid], nums[r]]: 收左 l = mid+1 # 不含mid,含右端 else: 否则去另一半 r = mid-1易错点
面试追问把动画讲成自己的话
追问为什么旋转数组还能二分?
追问怎么判断到底是左半还是右半有序?
追问有重复元素(LC81)会怎样?
追问复杂度?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
基于时间的键值存储
LeetCode 981 · 中等 · 沿着 二分查找 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题