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

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [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

此题拿到手直接一个for循环遍历到尾部,找到最小值输出,心里还觉得怎么会搞这么简单的题,这有什么意义吗?于是打开题解发现too young too simple,

原来此题是考察面试者如何根据序列局部有序性使用二分法快速查找最小值。这里直接搬力扣官方题解如下, 没做过这道题的可以看下面力扣题解,已经做过这道题的小伙伴可以跳过官方题解,直接看后半部分会有不一样的收获。

一个包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:

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

 

 

 

在二分查找的每一步中,左边界为 low,右边界为high,区间的中点为 pivot,最小值就在该区间内。我们将中轴元素numbers[pivot] 与右边界元素 numbers[high] 进行比较,可能会有以下的三种情况:

第一种情况是 numbers[pivot] < numbers[high]。如下图所示,这说明 numbers[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

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

 

 

 

第二种情况是  numbers[pivot] > numbers[high]。如下图所示,这说明 numbers[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

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

 

 

 第三种情况是 numbers[pivot]==numbers[high]。如下图所示,由于重复元素的存在,我们并不能确定 numbers[pivot]究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论numbers[high]是不是最小值,都有一个它的「替代品」numbers[pivot],因此我们可以忽略二分查找区间的右端点。

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

 

当二分查找结束时,我们就得到了最小值所在的位置。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int low = 0;
        int high = numbers.size() - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (numbers[pivot] < numbers[high]) {
                high = pivot;
            }
            else if (numbers[pivot] > numbers[high]) {
                low = pivot + 1;
            }
            else {
                high -= 1;
            }
        }
        return numbers[low];
    }
};

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

 

以上就是力扣官方题解,看完之后我就思考为啥 

numbers[pivot] 和 numbers[high] 比较 而不是
numbers[pivot] 和 numbers[left] 进行比较呢?

于是我亲自操刀下写出如下代码 

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int i = 0, j = numbers.size() - 1, m=0;
        while (i < j) {
            m = (i + j) / 2;
            if (numbers[m] > numbers[i]) i = m+1;
            else if (numbers[m] < numbers[i]) {i++;j = m;}
            else i++;
        }
        return numbers[i];
    }
};

 提交后发现几十个测试用例没跑过

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

 

 

 看到输入[1, 3, 5],立马想到没考虑到输入本来就是有序的,所以加了一行代码

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int i = 0, j = numbers.size() - 1, m=0;
        if(numbers[j] > numbers[i]) return numbers[i];
        while (i < j) {
            m = (i + j) / 2;
            if (numbers[m] > numbers[i]) i = m+1;
            else if (numbers[m] < numbers[i]) {i++;j = m;}
            else i++;
        }
        return numbers[i];
    }
};

 一提交,又没跑过,但通过的测试用例增加了,心里窃喜

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

 

 

 想不通为啥 numbers[pivot] 和 numbers[high] 比较 直接pass,跟numbers[low]比较就有问题呢?

原因:

        if (numbers[m] > numbers[i]) i = m+1; 执行这条语句后,有可能i刚好等于或者大于最小值索引,那么之后的循环将跳出最小值
            else if (numbers[m] < numbers[i]) {i++;j = m;}
            else i++;// 当两者相等时,有可能i也刚好等于或者大于最小值索引,那么循环后,i和j之间的数据已经全部有序,最小值也跳过了



分析出问题原因后,修改代码如下:

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int i = 0, j = numbers.size() - 1, m=0;
        while (i < j) {
            m = (i + j) / 2;
            if(numbers[j] > numbers[i]) return numbers[i];
            if (numbers[m] > numbers[i]) i = m+1;
            else if (numbers[m] < numbers[i]) {i++;j = m;}
            else i++;
        }
        return numbers[i];
    }
};

  提交通过

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

 

上一篇:养猪日记 2022.2.22


下一篇:11.易用性