剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1

示例 2:

输入:[2,2,2,0,1]
输出:0


思路1:遍历

题目的意思:输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
话说,管他是不是旋转数组,直接找出并返回数组的最小值即可。
也就是:找出数组的最小值
从前往后比较,遇到更小的替换,最后找出最小值
不难写出代码:

class Solution {
    public int minArray(int[] numbers) {
        //边界条件
        if(numbers == null || numbers.length == 0) return 0;
        
        int min = numbers[0];
        for(int i = 0; i<numbers.length; i++){
            if(numbers[i] < min)
            {
                min = numbers[i];
            }
        }
        return min;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)


思路2:遍历的优化

但是,这道题应该不是让我们这样解的,因为没有使用到原数组是排序好的数组这个信息。

假如,我们将输入的旋转后的数组再次改变为排序好的数组,那么,直接找出数组第一个元素就是最小值。
如何将输入后的旋转后的数组再次改变为排序好的数组呢?
可以从前往后找,找到一个前面的数字大于后一个数字的地方
可以从后往前找,找到一个前面的数字大于后一个数字的地方

其实,仔细想想,又没有让我们恢复排序数组,那么就没有必要对数组进行操作
那么,我们只需要找到一个前面的数字大于后一个数字的地方即可

从前往后找:

class Solution {
    public int minArray(int[] numbers) {
        //边界条件
        if(numbers == null || numbers.length == 0) return 0;
        
        //从前往后,找出第一个前面的数,大于后面的数的后面的数
        int min = numbers[0];
        for(int i = 0; i<numbers.length; i++){
            if(numbers[i] < min)
            {
                min = numbers[i];
                break;
            }
        }
        return min;
    }
}

比之前的程序,只是多了一个break


看了答案,解题是使用的二分查找
这倒也对,排序的数组,使用二分查找效率要高
但,问题是,这个数组也并不是排序的数组,而是排序数组的旋转
这样看来,这道题是考察二分查找的变种,如何在旋转后的排序数组中,使用二分查找找到最小值

思路3:二分查找

假设:
最小值所在的位置为i,值nums[i] = x;
那么,位置i之前的数据都小于x;位置i之后的数据都大于或等于x

为何前面是小于,后面是大于或等于呢?

比如[1,1,2,2,0,0]
那么,最小值的位置index可以是4,也可以是5,因为两个位置的值都是0
但,根据是排序的规则,我们可以默认第一个最小值为最小值,也就是index = 4的地方为最小值。
但是,最后求的是最小值,而不是位置,所以,index = 4或 index = 5都可以,反正最后返回的值都是0

其实,这里,你也可以规定i之前的数据都是小于或等于x,i之后的数据都是大于x。

根据上面的特征,可以得到nums[middle]的值,然后分情况讨论:

  1. 如果nums[middle] > nums[height],说明最小值在middle的右边

在这里插入图片描述

  1. 如果nums[middle] < nums[height],说明最小值在middle的左边

在这里插入图片描述

  1. 如果nums[middle] == nums[height]
    在这里插入图片描述

那么,最小值不能确定在左边还是右边,
不能减少为minArray(numbers, low, middle);,有可能把最小值在右边的情况忽略掉
但是,可以使得height - 1,这是因为,height - 1最多是middle的地方,也没有把最小值忽略掉

根据以上思路,尝试写代码:


class Solution {
    public int minArray(int[] numbers) {
        //边界条件
        if(numbers == null || numbers.length == 0) return 0;
        
        return minArray(numbers, 0, numbers.length - 1);
    }

    //左闭右开
    public int minArray(int[] numbers, int low, int height)
    {
        // int middle = (height - low)/2;
        int middle = low + (height - low)/2;
                    // = low + height/2 - low/2
                    // = height/2 + low/2;

        if(low < height){
            if(numbers[middle] > numbers[height]){
                return minArray(numbers, middle + 1, height);
            }else if(numbers[middle] < numbers[height]){
                return minArray(numbers, low, middle);
            }else{
                return minArray(numbers, low, --height);
            }
        }else{
            return numbers[low];
        }

        
    }
}

需要注意的地方:
int middle = (height - low)/2;❌
int middle = low + (height - low)/2;✅
int middle = (height + low)/2;✅

–height,因为要用到height - 1的结果

为何:minArray(numbers, middle + 1, height);
为何:minArray(numbers, low, middle);
你们不觉得这很怪异吗?
为何前面的middle可以+1操作,而后面那个middle不可以做-1操作?

为何minArray(numbers, middle + 1, height);
能不能+1,是看+1后,会不会把最小值省略掉
如第一张图,nums[middle + 1] > nums[middle],所以不会省略最小值
即使,nums[middle + 1]正好就是最小值,nums[middle + 1] < nums[middle]
而求的是minArray(numbers, middle + 1, height);,依然不会省略掉最小值,所以,此处middle + 1是可以的

为何minArray(numbers, low, middle);
能不能middle - 1,是看-1后,会不会把最小值省略掉
如第二张图所示,nums[middle - 1] < nums[middle],不会省略最小值
问题是,如果nums[middle]此时正好就是最小值,那么,nums[middle - 1] > nums[middle],此时minArray(numbers, low, middle - 1);就会把最小值nums[middle]给漏掉,因此,middle-1是不可以的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值