leetcode:154. 寻找旋转排序数组中的最小值 II

题目来源

题目描述

在这里插入图片描述

题目解析

思路

  • 旋转排序数组nums可以被拆分成2个排序数组nums1,nums2
    • 左边是一个递增数组
    • 右边也是一个递增数组
    • 左右两部分相交的位置出现了一个异常点,小的数字在大的数字后面

那么,只要我们可以找到异常点,也就找到了旋转数组的最小元素。

  • 从下面我们也可以看出 nums1任一元素 >= nums2任一元素
  • 因此,考虑二分法寻找此两数组的分界点nums[i](即第二个数组的首个元素)

在这里插入图片描述

方法

  • 设置 left, right指针在 nums数组两端,mid为每次二分的中点:
    在这里插入图片描述

    • 如果 nums[mid] > nums[right]时,mid一定在第一个排序数组中,i一定满足mid < i <= right,因此令left = mid + 1
    • 如果nums[mid] < nums[right]时,mid 一定在第 2 个排序数组中,i 一定满足 left < i <= mid,因此执行 right = mid;
    • 如果nums[mid] == nums[right]时,是此题对比 153题 的难点(原因是此题中数组的元素可重复,难以判断分界点 i 指针区间);
      • 例如 [1, 0, 1, 1, 1]和 [1, 1, 1, 0, 1],在 left = 0, right = 4, mid = 2 时,无法判断 midmid 在哪个排序数组中。
      • 我们采用 right = right - 1 解决此问题:
class Solution {
public:
    int findMin(vector<int>& nums) {
        int L = 0, R = nums.size() - 1;
        while (L < R){
            int M = (L + R) / 2;
            if(nums[M] > nums[L]){
                L = M + 1;
            }else if(nums[M] < nums[L]){
                R = M;
            }else{
                R = R - 1;
            }
        }
        return nums[L];
    }
};

动画

如果想不到 right = right - 1 怎么办?我们可以从头到尾遍历一下剩下的区间,找到那个最小的元素。

class Solution {
    public int minArray(int[] numbers) {

        // 设置 left, right 指针分别指向 numbers 数组左右两端
        // left 指向当前区间的最左边位置,所以初始化为 0
        int left = 0;
        
        //  right 指向当前区间的最右边位置,所以初始化为 nums.length - 1
        int right = numbers.length - 1;
        

        // 循环进行二分查找,直到左端点位置超过了右端点
        // 或者在循环过程中找到了起始位置
        while (left < right) {
            
            // mid 为中点(这里向下取整,比如 ( 2 + 7 )/ 2 = 4 )
            int mid = (left + right) / 2;
            
            // 当 mid 点所在元素大于数组末端的元素时,由于原来的数组是递增有序的,此时出现了异常,大的数在前面
            // 所以旋转点在 [ mid + 1, end ] 区间里面
            if (numbers[mid] > numbers[right]){
                
                // 所以旋转点在 [ mid + 1, end ] 区间里面 ,更新 left 的位置为 mid + 1
                left = mid + 1;
            
            // 当 mid 点所在元素小于数组末端的元素时,由于原来的数组是递增有序的
            // 所以旋转点在 [ left, mid ] 区间里面
            }else if (numbers[mid] < numbers[right]){
                
                // 旋转点在 [ left, mid ] 区间里面 ,更新 right 的位置为 mid 
                right = mid;
                
            // 此时,出现了 numbers[mid] = numbers[end] 的情况,无法判断
            // [ start , mid ]  为有序数组区间
            // 还是  [ mid , end ]  为有序数组区间
            // 比如: [1, 0, 1, 1, 1] 和  [1, 1, 1, 0, 1]
            }else{
                //  所以这里采取遍历的方式
                return findMin(numbers,left,right);
                
            }
        }
        return numbers[left];
    }
    
     // 从头到尾遍历 numbers ,获取到最小值
     public int findMin(int[] numbers,int left,int right){
        
        // 默认为数组的第一个元素为最小值
        int result = numbers[left];
         
        // 从头到尾遍历 numbers
        for(int i = left;i <= right;i++){

            // 当发现此时遍历的元素值小于 result
            if (numbers[i] < result) {
                // 那么更新 result
                result = numbers[i];
            }
        }
        // 返回 numbers 中的最小值
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值