寻找旋转排序数组中的最小值 图解题解
旋转数组总有一半是完整升序——靠 mid 和右端点那一比,每步排掉没有最小值的半边。
旋转数组像一把尺子从中间折过一次:整体乱了,但两段各自还是顺序的。找最小值就是找「折痕左边那个断崖」。把 nums[mid] 和右端点比:mid 比 r 大,说明从 mid 到 r 中间有掉崖,最小值在右半;mid 比 r 小,说明 mid 到 r 是完整升序,最小值在 mid 或其左边。每次砍掉没有断崖的一半,O(log n) 找到折痕。
这道题到底在问什么
- nums
- [4,5,6,7,0,1,2]
- 输出
- 0
先想最直接的笨办法
挨个比当然能找到最小,可那是 O(n),白白浪费了「旋转前本来有序」。题目把有序明牌摆这了,得用二分把它砍到 O(log n)。(动画第 3 步)
最优解:一步一步想明白
- 3挨个比当然能找到最小,可那是 O(n),白白浪费了「旋转前本来有序」。题目把有序明牌摆这了,得用二分把它砍到 O(log n)。
- 4转折:最小值就是旋转点。怎么二分定位它? 拿 nums[mid] 和右端点 nums[r] 比——这是 不变量:若 nums[mid] > nums[r],说明从 mid 到 r 中间有断崖, 最小值在 mid 右边(l=mid+1);若 nums[mid] < nums[r],说明 mid 到 r 已经是连续升序, 最小值在 mid 或它左边(r=mid,别丢 mid)。为什么不和 nums[l] 比?因为左半整段可能都比最小值大,比左端判不出方向。
- 5l=0, r=6搜索范围是整个数组,l=0、r=6。比较的「锚」永远是当前的右端 nums[r]。
- 6mid = (0+6)//2 = 3中点 mid=3,nums[3] = 7。和右端点 nums[6]=2 比。
- 77 > nums[r]=2 → 最小在右7 > 2,说明从 mid 走到 r 中间一定 掉过崖(旋转点在右半),最小值在 mid 右边,且 mid 本身肯定不是最小。
- 8l = mid+1 = 4l 跳到 mid+1=4,左半 [4,5,6,7](灰)整段丢掉——这半里没有最小值。范围缩到 [4, 6]。
- 9mid = (4+6)//2 = 5范围 [4,6],新中点 mid=5,nums[5] = 1。继续和右端 nums[6]=2 比。
- 101 < nums[r]=2 → 最小在左(含 mid)1 < 2,说明从 mid 到 r 是 连续升序(没掉崖),那这段最小的就在 mid 这头。最小值在 mid 或它左边,mid 可能就是答案。
- 11r = mid = 5r 收到 mid=5( 不是 mid−1,因为 mid 自己可能就是最小)。下标 6 丢掉,范围 [4, 5]。
- 12mid = (4+5)//2 = 4范围 [4,5],mid=4,nums[4] = 0。和右端 nums[5]=1 比。
- 130 < 1 → r = mid = 4,l==r 停0 < 1,最小值在 mid 或其左,r 收到 4。现在 l == r == 4,区间只剩一个,循环结束。
- 14nums[l] = nums[4] = 0l 和 r 撞在下标 4,nums[4] = 0 就是最小值。每轮砍一半,O(log n) 搞定。
- 17旋转找最小,记死一句:和右端点比。大于则断崖(最小值)在右,否则在左含 mid。和 nums[l] 比会判错,因为左半可能整段偏大。
⚠️ 容易写错的地方
✗ 错:拿 nums[mid] 和 nums[l] 比
✓ 对:和 nums[r] 比
左半可能整段都比最小值大,和左端比判不出最小值在哪边;只有和右端比才能稳定定位断崖
✗ 错:nums[mid] < nums[r] 时写 r = mid - 1
✓ 对:r = mid(不减 1)
mid 自己可能就是最小值,减 1 会把它丢掉,结果偏大或越界
✗ 错:循环写成 while l ≤ r
✓ 对:while l < r
本题靠 l==r 收尾返回 nums[l];用 ≤ 且 r=mid 不减会在 l==r 时死循环
完整代码(Python / C++ / Java)
Python
class Solution:
def findMin(self, nums):
l, r = 0, len(nums) - 1
while l < r:
mid = (l + r) // 2
if nums[mid] > nums[r]: # 断崖在右 → 最小在 mid 右
l = mid + 1
else: # 最小在 mid 或其左
r = mid
return nums[l]C++
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[r]) l = mid + 1; // 断崖在右
else r = mid; // 最小在 mid 或左
}
return nums[l];
}
};Java
class Solution {
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[r]) l = mid + 1; // 断崖在右
else r = mid; // 最小在 mid 或左
}
return nums[l];
}
}套路模板 · 旋转找最小(背下来)
l, r = 0, len(nums) - 1
while l < r:
mid = l + (r - l) // 2
if nums[mid] > nums[r]:
l = mid + 1 # 最小在右
else:
r = mid # 最小在左(含 mid)
return nums[l]复杂度
时间复杂度
O(log n)
每轮把区间砍掉一半
空间复杂度
O(1)
只用 l、r、mid 三个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 寻找旋转排序数组中的最小值 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
核心思路是什么?+
旋转后数组分两段升序;二分用 nums[mid] 和 nums[r] 比:> 则最小在 mid 右(l=mid+1),否则在 mid 左含 mid(r=mid);l==r 即最小值。
和找目标值的二分有何不同?+
没有具体 target,靠「和右端比」判断最小值在哪半;循环 l<r(不是≤),收缩 r=mid 保留候选,最后 l==r 落在最小值。
有重复元素(LC154)怎么办?+
nums[mid]==nums[r] 时无法判断,退化处理 r-=1(去掉一个重复右端),最坏 O(n)。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。