题目描述
题目链接: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];
}
};