剑指offer11 旋转数组中最小的数

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目描述了半天,其实就是让你找出数组中最小的数,有很多种方法,可以临时定义一个最小值,然后遍历,如果数值比当前最小值小,就更新最小值。遍历完成后返回即可。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        if(numbers.empty()) return 0;
        if(numbers.size() == 1) return numbers[0];
        int length = numbers.size();
        int temp = INT_MAX;
        for(int i = 0;i<length;i++){
            if (temp > numbers[i]) temp = numbers[i];
        } 
        return temp;
    }
};

也可以放到数组里,快排一下,返回数组第一个数,甚至可以用小顶堆。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        if(numbers.empty()) return 0;
        if(numbers.size() == 1) return numbers[0];
        int length = numbers.size();
        int array_temp[length];
        for(int i = 0;i<length;i++)
            array_temp[i] = numbers[i]; 
        sort(&array_temp[0],&array_temp[length]);
        return array_temp[0];
    }
};

言归正传,题目费了半天口舌让你找最小数字,显然不是让你用以上的方法求解,题目中给的是【旋转数组】,旋转前是【递增排序】的,旋转后,以【最小值】为界,两边依然是【递增】的状态。

这样来看的话,我们只要找到那个所谓的【分界点】,也就是最小值了。如果说暴力法的复杂度是 O(n)的话,那么我们当然希望在O(log(n))来解决这一问题了,二分法呼之欲出。

找到中间的数值value_med、起始数值value_start、末尾数字value_end,如果value_med => value_start说明在【前半段数组】依然是【递增】的,那么,临界点一定处于数组的【后半段】,更新value_med、value_start,继续在【后半段】中查找。

如果value_med < value_start,说明这个【临界点】在数组的【前半段】了,继续在数组的前半段进行二分查找。

根据以上两种条件,分而治之,可以较快定位出【临界点】的位置

道理很简单,但是还是有很多特殊情况需要加以考虑才能完整的解决所有case

大概有以下几个问题需要思考清楚才能写
1:迭代终止条件是什么?
2:判断条件到底应该是 > 还是 >=?要不要用<=?
3:是否存在特殊情况?特殊的情况该如何处理

一个一个来分析
1:迭代终止条件是什么?换句话说,就是什么时候我们找到这个数了。
头指针一直在尾指针的前面,如果不断二分,最后头指针与尾指针是相邻的,此时便是终止条件
2:到底要不要用等于号,如果是这样一个数列【2,2,2, 0, 1】
med = 2 start=2,临界点是在后半段,所以我们需要加上“=”的。
从题目给的例子来看,这里的递增就是非递减,所以单纯用< >是不够的
3:特殊情况存在吗?
先说第一种特殊情况,数组本来就是递增的,没旋转,那么我们就不需要二分查找,同时返回头就行了,因此while的控制条件就是要求start>=end;我们把med初始化为头指针,这样return的结果也不需要变了

再说第二种情况,如果数组是【3,0,3 , 3 ,3】,我们会发现med=start的时候,经过第一次判断,会认为最小数位于后半段,其实不然。所以需要在循环中加一个判断,如果出现这种情况,我们就用暴力法去查找做小值

这样的程序一次写好是不可能的,我们需要先写一个简单的demo,然后逐步去完善demo,逐步考虑到这些问题,这也是刷题的目的之一,面试的时候没有太多时间,也比较紧张,或许不能考虑的如此周全,因而我们需要提前做做题,这样才能面试的时候写出好的代码

class Solution {
public:
    int minfind(int* number,int start,int end){
        int res = number[start];
        for(int i = start + 1;i<=end;i++){
            if(number[i] < res)
                res = number[i];
        }
        return res;
    }

    
    int minArray(vector<int>& numbers) {
        if(numbers.empty()) return 0;
        int length = numbers.size();
        if(length == 1) return numbers[0];
        //if(numbers[0] == numbers[length-1] ) return numbers[0];
        int start_pos = 0;
        int end_pos = length-1;
        int med_pos = start_pos;
        while( numbers[start_pos] >= numbers[end_pos]){
            if((end_pos - start_pos)==1) {
                med_pos = end_pos;
                break;
            }
            med_pos = (end_pos + start_pos)>>1;
            if(numbers[start_pos] == numbers[end_pos] && numbers[med_pos] == numbers[end_pos])
               return minfind(&numbers[0],start_pos,end_pos);
            if(numbers[med_pos] >= numbers[start_pos]){
                start_pos = med_pos;
            }else if(numbers[med_pos] <= numbers[end_pos]){
                end_pos = med_pos;
            }
        }

        return numbers[med_pos];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值