【题解】旋转数组的最小数字

题目

题目来源:leetcode 剑指Offer 11:旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
示例1:

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

示例2:

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

示例3:

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

解题思路

旋转前的数组是单调不减的,旋转后的数组会出现0个(将数组中的前0个元素移动到后面)或1个断崖。若没有断崖,数组中第一个元素就是最小值;若有,旋转后的数组仅在断崖处是递减的,且较小值(崖底)是数组的最小值。
没有断崖:
0个断崖
有断崖:
1个断崖
使用二分查找在数组numbers下标[left..right]序列内寻找断崖(或判断是否存在断崖)。如果中间元素numbers[mid] < numbers[mid - 1],则mid对应着数组的最小值。
如果numbers[mid]大于numbers[right],说明断崖在右半部。
在右半部查找
如果numbers[mid]小于numbers[right],说明断崖在左半部。
在这里插入图片描述
如果numbers[mid] == numbers[right],则无法判断断崖的位置,需要将right1,重新做判断。
断崖在右半部:
无法判断断崖的位置
断崖(最小值)在左半部:
在这里插入图片描述
如果left > right时还未找到最小值,该数组无断崖。

代码实现(C++)

int minArray(vector<int>& numbers) {
        if (numbers.size() < 2) {
            return numbers.front();
        }
        int left = 0;
        int right = numbers.size() - 1;
        // 二分查找
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 找到了断崖
            if (mid > 0 && numbers[mid] < numbers[mid - 1]) {
                return numbers[mid];
            }
            if (numbers[mid] == numbers[right]) {
                --right;
            }
            else if (numbers[mid] < numbers[right]) {
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        // 没有断崖
        // 使用"return numbers[left];"语句也能通过所有的测试用例
    	return numbers.front(); 
    }

以下代码也能通过所有的测试用例。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        if (numbers.size() < 2) {
            return numbers.front();
        }
        int left = 0;
        int right = numbers.size() - 1;
        while (left < right) { // 去掉了=符号
            int mid = left + ((right - left) >> 1);
            if (mid > 0 && numbers[mid] < numbers[mid - 1]) {
                return numbers[mid];
            }
            if (numbers[mid] == numbers[right]) {
                --right;
            }
            else if (numbers[mid] < numbers[right]) {
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        return numbers[left]; 
    }
};

二分查找

如果在数组下标[left..right]范围内根据某种要求进行二分查找,笔者原以为while循环控制条件left < rightleft <= right是等价的,只要最后return left,那结果就是正确的。通过本题可知,在旋转的有序数组上进行二分查找时循环控制语句left < rightleft <= right是等价的。
但事实上,如果循环控制条件是left < right,当left == right时退出循环,而此时left所对应的元素未必满足要求。因此正确的循环控制条件应该是left <= right
例如要在数组nums=[1,3,5]中找到第一个大于等于2的元素,开始left = 0,right = 2,mid = 1,因为nums[1]符合要求,因此将right置为0,此时left == right。如果循环控制语句是left < right,那将返回nums[0],得到错误的结果;如果循环控制语句是left <= right,那将进一步对nums[0]进行判断,最后返回nums[1],结果正确。
由此可见,分别在正常的有序数组和旋转后的有序数组上使用二分查找时,两者有一些微妙的区别。

扩展

要在旋转的有序数组上进行查找操作,二分查找是首先要考虑的方法。但由于旋转数组是局部有序的,所以要区分有序段和无序段(存在断崖),还要注意出现重复元素。
leetcode 面试题10.03:搜索旋转数组是在旋转数组中搜索第一个出现的元素,除了二分查找,还用到了递归的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值