算法题 寻找旋转排序数组中的最小值

LeetCode 153. 寻找旋转排序数组中的最小值

问题描述

给定一个长度为 n升序排列数组,经过 1 到 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 次得到输入数组。

算法思路

核心:二分查找的变种

  1. 旋转数组特性
    • 数组由两个有序部分组成:左段(较大值)和右段(较小值)
    • 最小值是右段的第一个元素
  2. 二分查找策略
    • 比较中间元素 nums[mid] 与右边界 nums[right]
    • nums[mid] > nums[right]:最小值在右半段(left = mid + 1
    • nums[mid] < nums[right]:最小值在左半段(right = mid

代码实现

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        
        while (left < right) {
            int mid = left + (right - left) / 2; // 避免溢出,mid = left
            
            // 中间值大于右边界:最小值在右半段
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } 
            // 中间值小于等于右边界:最小值在左半段(含mid)
            else {
                right = mid;
            }
        }
        return nums[left]; // left == right 时找到最小值
    }
}

算法分析

  • 时间复杂度O(log n)
    每次循环将搜索范围减半。
  • 空间复杂度O(1)
    仅使用常数空间。

算法过程

输入 nums = [4,5,6,7,0,1,2]

初始:left=0, right=6
1: mid=3 → nums[3]=7 > nums[6]=2 → left=4
2: [0,1,2] 中 mid=5 → nums[5]=1 < nums[6]=2 → right=5
3: [0,1] 中 mid=4 → nums[4]=0 < nums[5]=1 → right=4
结束:left=right=4 → 最小值 nums[4]=0

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准旋转
    int[] nums1 = {4,5,6,7,0,1,2};
    System.out.println("Test 1: " + solution.findMin(nums1)); // 0
    
    // 测试用例2:旋转到最小值在开头
    int[] nums2 = {0,1,2,4,5,6,7};
    System.out.println("Test 2: " + solution.findMin(nums2)); // 0
    
    // 测试用例3:最小值在中间
    int[] nums3 = {7,0,1,2,4,5,6}; 
    System.out.println("Test 3: " + solution.findMin(nums3)); // 0
    
    // 测试用例4:完全逆序
    int[] nums4 = {3,2,1};
    System.out.println("Test 4: " + solution.findMin(nums4)); // 1
    
    // 测试用例5:两个元素
    int[] nums5 = {2,1};
    System.out.println("Test 5: " + solution.findMin(nums5)); // 1
    
    // 测试用例6:单元素
    int[] nums6 = {5};
    System.out.println("Test 6: " + solution.findMin(nums6)); // 5
    
    // 测试用例7:严格递增(旋转后)
    int[] nums7 = {1,2,3,4,5}; 
    System.out.println("Test 7: " + solution.findMin(nums7)); // 1
}

关键点

  1. 比较对象选择

    • 使用 nums[mid]nums[right] 比较(而非 nums[left]
    • 原因:当数组完全有序时,nums[left] 可能误导判断(如测试用例7)
  2. 边界更新逻辑

    • nums[mid] > nums[right]left = mid + 1
      (最小值在右半段,且 mid 不可能是最小值)
    • nums[mid] < nums[right]right = mid
      (最小值在左半段,且 mid 可能是最小值)
  3. 循环终止条件

    • left < right 保证最终 left == right 时退出
    • 此时指向最小值

常见问题

1. 为什么不用比较 nums[left]
当数组完全有序时(如 [1,2,3,4,5]):

  • 若比较 nums[mid]nums[left]3>1 会错误移动左边界
  • 正确做法:比较 nums[mid]nums[right]3<5 移动右边界

2. 如何处理重复元素?
本题假设数组元素唯一。若存在重复元素(如 LeetCode 154),需额外处理:

  • nums[mid] == nums[right] 时,right--
    (因为无法判断最小值位置)

3. 为什么 right = mid 而不是 mid - 1
nums[mid] < nums[right] 时,mid 可能是最小值(如测试用例5的 [2,1]),不能跳过。

4. 最坏情况时间复杂度?
二分查找保证最坏情况下仍为 O(log n),例如:

  • 每次排除一半元素
  • 即使最小值在中间(如测试用例3),复杂度不变

5. 算法是否兼容旋转 0 次的情况?
兼容。当数组完全有序时:

  • 每次比较 nums[mid] < nums[right]right = mid
  • 最终收敛到 left=0(最小值位置)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值