【力扣100题】80.寻找旋转排序数组中的最小值

题目描述

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次旋转后,得到输入数组。

例如,原数组 nums = 0,1,2,4,5,6,7 在变化后可能得到:

  • 若旋转 4 次,则可以得到 4,5,6,7,0,1,2
  • 若旋转 7 次,则可以得到 0,1,2,4,5,6,7

注意,数组 a\[0, a1, a2, ..., an-1] 旋转一次的结果为数组 a\[n-1, a0, a1, a2, ..., an-2]。

给你一个元素值互不相同的数组 nums,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

复制代码
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

复制代码
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= numsi <= 5000
  • nums 中的所有整数互不相同
  • nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 特点
二分查找(标准版) 比较 numsmid 与 nums.back(),判断最小值在哪个区间 O(log n) O(1) 标准解法,最常考
二分查找(变形) 比较 numsmid 与 nums0,判断最小值在哪个区间 O(log n) O(1) 另一种判断方式
遍历找最小值(不推荐) 线性扫描数组 O(n) O(1) 不满足题目要求,仅作对比

方法一:二分查找(与 nums.back() 比较)

代码实现

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int l = -1, r = nums.size() - 1;
        while (l + 1 < r) {
            int m = l + (r - l) / 2;
            if (nums[m] < nums.back()) {
                r = m;
            } else {
                l = m;
            }
        }
        return nums[r];
    }
};

核心思想

利用旋转排序数组的特性:

  • 数组被分成两段有序数组,第二段的第一个元素就是最小值
  • 通过比较 numsm 与 nums.back()(末尾元素)的大小关系,判断 mid 落在哪一段
  • 如果 numsm < nums.back(),说明 mid 在第二段(最小值在 l, m 区间)
  • 否则 numsm >= nums.back(),说明 mid 在第一段(最小值在 m, r 区间)

算法流程图

复制代码
以 nums = [4,5,6,7,0,1,2] 为例:

数组结构分析:
  第一段:[4,5,6,7] 所有元素 > nums.back()=2
  第二段:[0,1,2] 所有元素 <= nums.back()=2
  最小值 = 0,在第一段和第二段的交界处

二分查找过程:
初始:l=-1, r=6
第1轮:m=(-1+6)/2=2,nums[2]=6 >= nums.back()=2,l=m=2
第2轮:m=(2+6)/2=4,nums[4]=0 < nums.back()=2,r=m=4
第3轮:m=(2+4)/2=3,nums[3]=7 >= nums.back()=2,l=m=3
第4轮:m=(3+4)/2=3,l+1=4==r,循环结束
结果:nums[r]=nums[4]=0

最小值:0

逐行解析

cpp 复制代码
int l = -1, r = nums.size() - 1;

初始化二分边界:

  • l = -1,表示"哨兵"位置,在数组最左边(实际不存在)
  • r = nums.size() - 1,指向数组最后一个元素

注意:使用 l = -1 而不是 l = 0,使得最终返回值是 numsr,而 r 最终指向最小值位置。


cpp 复制代码
while (l + 1 < r) {

循环条件:l + 1 < r,即 left 和 right 之间至少有一个元素。

当 l + 1 == r 时,l 和 r 相邻,循环结束,此时 r 指向最小值。


cpp 复制代码
int m = l + (r - l) / 2;

计算中间位置,使用防溢出写法。


cpp 复制代码
if (nums[m] < nums.back()) {
    r = m;
} else {
    l = m;
}

关键判断逻辑:

  • numsm < nums.back():说明 mid 在第二段(因为第二段所有元素都 <= nums.back()),最小值在 l, m 区间,更新 r = m
  • 否则:说明 mid 在第一段,最小值在 m, r 区间,更新 l = m

cpp 复制代码
return nums[r];

循环结束时,r 指向最小值位置,返回该值。

复杂度分析

复杂度 分析
时间 每次循环将区间缩小一半,最多 O(log n) 次
空间 O(1),只使用了常数个变量

方法二:二分查找(与 nums0 比较)

代码实现

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        int left = 0, right = n - 1;
        
        while (left < right) {
            int mid = left + (right - left) / 2;
            
            if (nums[mid] < nums[0]) {
                // mid 在第二段,最小值在 [left, mid]
                right = mid;
            } else {
                // mid 在第一段,最小值在 [mid+1, right]
                left = mid + 1;
            }
        }
        
        // 如果 left == 0,说明数组没有旋转
        if (left == 0 && nums[left] <= nums[n - 1]) {
            return nums[0];
        }
        
        // 否则返回最小值(就是 left 位置)
        return nums[left];
    }
};

核心思想

与 nums0 比较来判断 mid 落在哪一段:

  • numsmid < nums0:mid 在第二段,最小值在 left, mid
  • numsmid >= nums0:mid 在第一段,最小值在 mid+1, right

与方法一的对比

对比项 方法一(与 nums.back() 比较) 方法二(与 nums0 比较)
比较对象 numsmid vs nums.back() numsmid vs nums0
边界初始化 l = -1, r = n - 1 left = 0, right = n - 1
返回值 numsr numsleft 或 nums0
循环条件 l + 1 < r left < right

复杂度分析

复杂度 分析
时间 O(log n)
空间 O(1)

方法三:直接遍历(不推荐)

代码实现

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int minVal = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            minVal = min(minVal, nums[i]);
        }
        return minVal;
    }
};

复杂度分析

复杂度 分析
时间 O(n),需要遍历整个数组
空间 O(1)

不推荐原因

题目要求时间复杂度为 O(log n),遍历不满足要求。但该方法思路简单直接。


边界情况分析

情况1:数组只有一元素

复制代码
输入: nums = [1]
分析: l=-1, r=0
     循环条件 l+1=-1+1=0 == r=0?不成立,循环不执行
结果: nums[r]=nums[0]=1

情况2:数组没有旋转(旋转了 n 次)

复制代码
输入: nums = [1,2,3,4,5](旋转了5次,等于没旋转)
分析: 按旋转定义,旋转 n 次后数组不变
结果: 最小值 = 1

情况3:旋转次数为 1(最小值在位置 1)

复制代码
输入: nums = [5,1,2,3,4](旋转1次)
分析: 最小值 1 在位置 1
结果: 1

情况4:旋转次数为 n-1(最小值在位置 n-1)

复制代码
输入: nums = [2,3,4,5,1](旋转4次)
分析: 最小值 1 在位置 4
结果: 1

情况5:数组完全倒序(旋转 n-1 次)

复制代码
输入: nums = [2,1](旋转1次)
分析: 最小值 1 在位置 1
结果: 1

情况6:原数组升序,未旋转

复制代码
输入: nums = [1,2,3,4,5]
分析: 由于题目说旋转 1 到 n 次,如果旋转 5 次则等于原数组
     方法一会正确处理
结果: 1

旋转排序数组特性总结

复制代码
原数组:[0, 1, 2, 4, 5, 6, 7](n=7)
旋转 k 次后的数组:[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]

旋转点(最小值位置)分析:
  旋转 0 次:[0,1,2,4,5,6,7] 最小值在位置 0
  旋转 1 次:[7,0,1,2,4,5,6] 最小值在位置 1
  旋转 2 次:[6,7,0,1,2,4,5] 最小值在位置 2
  旋转 3 次:[5,6,7,0,1,2,4] 最小值在位置 3
  旋转 4 次:[4,5,6,7,0,1,2] 最小值在位置 4
  旋转 5 次:[2,4,5,6,7,0,1] 最小值在位置 5
  旋转 6 次:[1,2,4,5,6,7,0] 最小值在位置 6
  旋转 7 次:[0,1,2,4,5,6,7] 最小值在位置 0(等于原数组)

关键发现:旋转点就是最小元素的位置。


面试追问 FAQ

问题 回答
如何找到旋转点? 旋转点就是最小元素的位置,通过二分查找可以高效定位
为什么比较 numsmid 和 nums.back()? nums.back() 是第二段的上界。如果 numsmid < nums.back(),说明 mid 在第二段
如果数组没有旋转怎么办? 旋转 n 次等于原数组,本题算法仍然正确,会返回 nums0
二分查找的循环条件 l + 1 < r 是什么意思? 表示 left 和 right 之间至少有一个元素,循环直到它们相邻
为什么初始化 l = -1 而不是 l = 0? 这样最终返回 numsr 就是最小值,而 r 最终指向最小值位置
如何处理数组为空的情况? 题目保证 n >= 1,不需要处理空数组
与第 33 题(搜索旋转排序数组)有什么区别? 本题找最小值,33 题找 target 值。本题更简单,只需要定位最小元素

相关题目

题目 难度 核心区别
153. 寻找旋转排序数组中的最小值(本题) 中等 数组中值互不相同
154. 寻找旋转排序数组中的最小值 II 困难 数组中值可能重复
33. 搜索旋转排序数组 中等 在旋转数组中查找 target
81. 搜索旋转排序数组 II 中等 在可能有重复值的旋转数组中查找 target
35. 搜索插入位置 简单 一维数组找插入位置

总结

要点 说明
核心思想 利用旋转数组两段有序的特性,通过二分查找定位最小值
关键判断 numsmid < nums.back() 说明 mid 在第二段(最小值所在段)
边界设置 l = -1, r = n - 1,使得最终返回 numsr
循环条件 l + 1 < r,直到 l 和 r 相邻
时间复杂度 O(log n)
空间复杂度 O(1)
防溢出 使用 mid = l + (r - l) / 2

相关推荐
倒头就睡的小比特3 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼3 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
m0_547486663 天前
《数据结构教程》全套 PPT课件2026
数据结构
旖旎夜光3 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark3 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her13 天前
并查集-听课笔记
笔记·算法·并查集
码流子3 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven3 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark3 天前
从算法设计模式看编程思维的抽象能力4
算法