剑指offer-旋转数组中的最小数字

题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

这个题目比较考察思路全面性,

首先它问的是数组中最小值,那我直接上去就用了STL的函数整个数组整体求min_element,结果竟然通过了,估计也没有突破提交的最大运行时间限制吧,但是这个问题的特点就在于需要抓住所给的条件,“非递减”,首先非递减我们必须得理解这个意思是“元素中后面的元素大于或者等于前面的元素”,因此目前大多数思路为 二分法,这个方法比较高效,能够省去很多不必要的比较操作,节省时间。但是我们也要注意的一点是如果元素中有重复元素,导致二分的左右两端和中间的值是相等的,就没有办法进行下一步的缩小问题规模,这时候只能进行线性遍历比较找出元素中后面元素小于前面元素的这个元素的位置,即为整个数组中旋转以后前后两个子数组的分割点,左边的子数组是被旋转到前面的后半部分非递减序列,右边也是原非递减数组的后半部分非递减子数组。

以下是我整合两个特殊情况和一般情况的得到AC的代码:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size()==0) return 0;//原数组为空
        int left = 0, right = rotateArray.size()-1;
        if(rotateArray[left]<rotateArray[right]) return rotateArray[left];//如果数组根本没有旋转,则最左边第一个元素就是最小元素
        int mid;
        while(left<right-1)//如果左右端点中间还有元素,即可以求mid值,则继续循环
        {
            mid = left+(right-left)/2;
            if(rotateArray[left]==rotateArray[mid]&&rotateArray[left]==rotateArray[right])//若出现特殊情况,不能二分法缩小寻找范围则需要从头到尾进行查找
                return *min_element(rotateArray.begin()+left,rotateArray.begin()+right);
            if(rotateArray[mid]>=rotateArray[left])
            {
                left = mid;//这里开始我不明白为什么不用mid+1,然后我才明白,要想继续往下缩小规模比较则需要模拟出原旋转数组一样的模式,左边有比右边大的元素,这样我们才能接着原先的思路往下继续二分查找
            }
            else
                right = mid;//如果mid大于left位置的元素,则最小元素就在mid之前,也就是寻找范围缩小为前半部分的子数组。
        }
        return min(rotateArray[left],rotateArray[right]);//当只剩下左右两边的元素,则此时右边的元素就是最下元素了
    }
};

对于旋转非递减数组的选取最小值的特殊情况:1、旋转长度为0,也就是根本没有旋转;则只需要返回原数组即可;2有一种情况是二分以后,左边/右边/中间的三个元素值相等,则此时,也就是说不能确定中间值是旋转的前半部分的值还是后半部分的值,不能确定最小值到底在哪一边。因此需要最笨的办法对这数组整体查询,时间复杂度是O(n);

一个博客对于几种特殊情况的说明很详细,请参阅:旋转数组中的最小数字

还可以用递归方法吧,再思考一下。。。后续补充

------------------------------------------------------递归分割线-----------------------------------------------------------------

这个方法的思路就是左右两个指针二分夹逼往中间靠,代码源于牛客网递归代码,感觉递归的思想还是比较难把握,说简单了就是简单,不就是假设子问题解决了我们现在用子问题来往上求解父问题嘛,但是递归体 也就是实现细节比较难想象,一想细节实现 就容易绕进去,那边有个山,山上有个庙,庙里有个老和尚,老和尚在讲一个故事:“那边有个山,山上有个庙,庙里有个老和尚,老和尚在讲一个故事.......不停的往里面绕” 但是重要的还是递归终止条件写好了,就可以往回回溯,还是递归难啊。

下面是代码的理解注释:

class Solution {
    int findMin(vector<int> a, int first, int last) {
        if (first >= last) return a[last];//实际上就是判断左右两边是不是越界了
        int mid = (first + last) / 2;
        if (a[first] == a[last] && a[mid] == a[first]) {//如果出现了特殊情况不能判断最小值在左右两边哪一边的话就只能用最笨全组查找的方法
            // linear search
            int min = a[first];
            for (int i = first + 1; i <= last; i++)
                min = a[i]<min ? a[i] : min;
            return min;
        }
        if (a[first] < a[last]) {
            return a[first];//如果左边小于右边的话说明左边这个就是查找的最小值
        } else {//如果左边大于右边则需要继续二分缩小查询范围进行查找
            if (a[mid] >= a[first]) {//如果中间值大于或者等于左边值,则说明这个中间值是原数组右边元素,现在被旋转到左边来了
                return findMin(a, mid + 1, last);//就说明左边现在整体是非递减数组,只能在现在的mid+1后面去查询
            } else {
                return findMin(a, first, mid);//如果中间值小于左边值,则说明右边的整体元素是非递减的,只能在左边子数组中查询最小元素
            }
        }
    }
  
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int n = rotateArray.size();
        if (n == 0) return 0;//当数组为空时,返回0,实际上是不科学的,如果查询出错一般都是返回-1嘛
        return findMin(rotateArray, 0, n - 1);
    }
};

猜你喜欢

转载自blog.csdn.net/qq_18548149/article/details/79513610
今日推荐