剑指刷题日记剑指 Offer 11. 旋转数组的最小数字

题目描述

题目链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/
在这里插入图片描述

解题思路

思路1:找最小值
因为递增的特性,找到第一个比数组首元素小的数就将它返回就可以了。
解答代码如下:

class Solution {
public:
    int minArray(vector<int>& numbers) 
    {
        int min = numbers[0];
        for(int i=0; i<numbers.size(); ++i)
        { 
            if(numbers[i]<min)
                return numbers[i]; // 找到第一个比它小的就行,因为后面也递增
        }

        // 没有找到的话说明数组本身已经是递增了(没有旋转或者全是相同元素)
        return min; 
    }
};

思路2:二分法
雪菜大神说过:二分不一定要有单调性,二分的本质是寻找某种性质的分界点。只要可以找到某种性质,使得区间的前半部分满足,后半部分不满足,那么就可以用二分把这个分界点找到。
因此,此题就相当于找出第一个比numbers[0]小的数
我们假设target = numbers[0]
我们拿官方的图来举例;
在这里插入图片描述
去掉末尾与numbers[0]相等的数。
去重之后,最右边的数应该小于target,如果依旧大于,说明此时数组依旧是单调递增的,画图举例如下:
在这里插入图片描述
那么此时最小值就是数组的第一个值,直接返回target。
否则的话,就是以下的情况。分成了两个区间,左边的都大于等于target,右边的都小于target。
在这里插入图片描述

雪菜大神提供了两个二分模板如下:
第一个模板用于求边界(例如找到第一个小于等于x的数)
划分区间为 [left, mid]和[mid+1, right] (把mid和left分在一起是因为任何时候mid都可以是答案)
在这里插入图片描述

第二个模板用于求边界(例如找到最后一个小于等于x的数)划分区间为 [left, mid-1]和[mid, right](把mid和right分在一起是因为任何时候mid都可以是答案
在这里插入图片描述

bool check(int x) {/* ... */} // 检查x是否满足某种性质
==========================================================
模板一,找左边界
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}

=========================================================
模板二,找右边界
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;   // 比上面多加了1
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;  
    // return r也行,因为while终止条件为 l==r,此时就一个数
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果是找某个值是否存在,那就更简单了


此时区间划分为 [l, mid-1] mid [mid+1, r]
while(l<r)
{
	int mid = l + r >> 1;
	if(arr[mid]==target)
		return mid;
	else if(arr[mid]<target)
		left = mid+1;
	else
		right = mid-1;
}

此题也就可以转换为找到第一个小于target的数,因为可以采用二分模板中求左边界的模板。由于if和else本身就是互斥的。由于我们区间被分成分成了两个区,左边的都大于等于target,右边的都小于target。所以可以灵活选择check(mid)为numbers[mid]<target或者numbers[mid]>=target。如果选择numbers[mid]<target,那么表明答案一定在左边,或者mid本身,因此压缩右边——right=mid,将区间划分为[l, mid]
if else本身就是互斥的,因此可以灵活调整,不要太死板。
解答代码如下:

class Solution {
public:
    int minArray(vector<int>& numbers) 
    {
        int target = numbers[0];
        int left = 0, right = numbers.size()-1;

		// right>0不能丢,因为只有一个数的话,去重之后 right=-1
        while(right>0 && numbers[right]==target)
            --right;   
        
        if(numbers[right]>target)   
            return target;

        // 开始二分
        while(left<right)
        {
            // 区间划分 [left, mid] 和 [mid+1, right];
            // 因为mid也可能为答案
            int mid = (left+right)>>1;
            if(numbers[mid]<target)
                right = mid;  // 答案就在左手边,包括mid本身
            else 
                left = mid+1;
			
			/*
			// 下面也行。因为if else本身就是互斥的,谁前谁后无所谓
            if(numbers[mid]>=target)
                left = mid+1;  // 答案肯定在右手边,压缩左边
            else 
                right = mid;
            */

        }
        // 结束时left = right,只有一个数了
        return numbers[left];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值