前言
前置知识,需要先掌握最基本的二分查找704. 二分查找
本文讲解题目为:278. 第一个错误的版本
本文讲解的是逼近类的二分(当然这是我自己取的名字),什么意思?
例如我们有一串数字1,2,3,3,3,3,3,4;若要求找到第一个大于2的数值的位置
显然第三个数是我们需要的数,但如何通过二分搜索到,这就是本文需要研究的。
纯二分搜索其实不难,但其中的各种细节实在让人抓狂
一般如何辨别一道题能用二分,我总结了一些规律
- 首先拥有连续的序列
- 能够通过暴力 O ( n ) O(n) O(n)的方法是搜索出答案
- 该序列具有二段性
模板题
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
}
};
其他练习题:
理解题意
有很多人不懂这个题意,这里简单抽象说一下
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
现有一个给定
api isBadVersion函数
,返回布尔值且随着给定的数值递增,返回值将由
false ---> true
因此出现序列
false false false false true true true true true true true
要求我们找到第一个返回
true
的位置,这就要求我们如何用二分去逼近这个位置
样例中每个测试样例是两个数,但实际借口只接受一个数
n:表示序列长度 [1, n]
bad:表示第几个是第一true,就是我们需要搜索的值,但这个数是隐藏起来的,类似于猜数游戏一样让你猜出来
解题思想
下面,我将以三种写法来理解性的解决该题
对于本题的一个核心搜索思想是:
设当前搜索
idx
位置,答案是ans
位置
- 若**idx为
true
,**则ans必在 包括idx的左侧- 若**idx为
false
,**则ans必在 不包括idx的右侧因此我们只考虑一段,另一端不断缩进
先说一个规律
- 若循环判断无等号, 则只有一边需要
±1
- 若循环判断有等号,则两边都需要
±1
循环判断无等号
若mid是true,则有可能mid就是第一个true,因此不能偏移。
因为这个位置可能就是我们要找的第一个true,因此直接让right接替mid的位置
false的话,必然需要偏移才能到达true的状态,至少是该位置的下一个位置,才可能是true
退出循环的条件是:
当且仅当 left == right
因此我们return 任意即可
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left < right) {
int mid = (right-left)/2 + left;
if (isBadVersion(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
// 退出循环时必然是 left == right
return left; // return right;一样
}
};
循环判断有等号
还是上面的原理,若mid为false
则
闭区间[left, mid]
之间全为false,不合格,left = mid + 1;
其余什么都不用想
让另一边right = mid - 1即可(因为循环中是有=的,不然就是出现死循环)
这种写法与最普通的二分查找完全一样,但是难点就在于如何return
退出循环的时候,要么是
left == right+1
,要么是left+1 == right
,因此不能随意return我们从偏移的原理入手
left 向右偏移一个单位,极端情况下,会让false发生质的变化,变为true 可行
right 向左偏移一个单位,极端情况下,会让true发生质的变化,变为false 不可行
所以说,我们这里只能return left;
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left <= right) {
int mid = (right-left)/2 + left;
if (!isBadVersion(mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
};
综合写法
回顾一下上面两种方法的优缺点
- 无等号
- 缺点:要明确哪一个需要偏移
±1
- 优点:随意return left或者right,不用顾虑
- 有等号
- 缺点:要明确return 哪一个
- 优点:循环中两边都要偏移,不用顾虑
我们来取其精华,取其糟粕一下
循环中若要都
±1
则必须循环判断中有等号,这个无法改变而关于return方面,不想纠结return哪一个,我们就引入第三个变量
我们将这第三个变量始终接受我们需要的值
且在循环中,我们不断的缩小区间,而这区间就是答案所在区间,退出循环前的那一轮,会夹击成区间长度为1的空间
我们在保证上面两个条件的情况下,必然可以得到我们想要的答案。
大家觉得这种写法是否有点无脑且耍赖的感觉呢
///(^v^)\\\
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
int ans = -1;
while (left <= right) {
int mid = (right-left)/2 + left;
if (isBadVersion(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}
};