二分的本质不是单调性。具有单调性一定可以二分,不具有单调性或许能够二分;
二分的本质类似“边界”:如果根据某个性质,把整个区间一分为二,一部分满足这个性质,另一部分不满足这个性质 ,二分就可以寻找两部分的边界
1.整数二分
存在边界问题。
-
使用循环来查找边界(目标)所在的下标。
check(mid)
表达式用来检查mid是否满足某种性质,根据结果是true还是false来分析区间的更新方式:1) true:l = mid; false: r = mid-1;
,2) true: r = mid; false: l = mid+1;
因为是整数二分,两个区间之间没有交点,两个模板分别是求两个边界的下标。显然两个边界点一定是紧挨着的。 -
这两种方式各自的true和false是一一对应的!注意如果是第一种情况改
mid = (l+r+1) / 2;
-
整体思路就是,首先打模板,然后画图分析出查询值x的满足性质以及划分区间,进一步写出true和false、修改mid。
注意while循环结束之后,l == r ,退出循环。
注意三个式子的对应关系:
mid = (l+r+1) / 2 <-- l = mid --> r = mid - 1
mid = (l+r) / 2 <-- r = mid --> l = mid + 1
. -
主要是看一下x到底是哪个边界,满足什么性质?
要么l = mid,要么r = mid.
#include<iostream>
using namespace std;
bool check(int x){/* 检查函数,或者是简单的表达式比较*/}
// 1)区间划分成[l,mid-1][mid,r]
int bsearch_1(int l,int r)
{
while(l < r)
{
int mid = (l+r+1) / 2 ;
if(check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
// 2)区间划分成[l,mid][mid+1,r]
int bsearch_2(int l, int r)
{
while(l < r)
{
int mid = (l+r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
- 二分循环,缩小后的区间都是目标x所在的区间,当区间长度是1的时候,那么这就是答案。二分一定是有解的,不同的题目可能会无解。也就是无解的时候不会找到满足等号的条件,二分找出来的就可能是单纯<或者>,而不带等号。
2.浮点数二分
浮点数二分不需要处理边界问题,当区间更新到只有很小的区间的时候,可以认为找到了答案。
想法是挺重要的,把一个问题转化为二分的问题…例如下面是使用浮点数二分求出输入值x的开方:
#include<iostream>
using namespace std;
int main()
{
double x;
cin >> x;
double l = 0, r = x;
for (int i = 0; i < 100; i++)
{
double mid = (l + r) / 2;
if (mid*mid > x) { r = mid; }
else l = mid;
}
cout << l << endl;
return 0;
}
注意浮点数二分都是l = mid 或者 r = mid !
key:
刷题反思
1. 0到n-1中缺失的数字
数组方面要考虑下标和值的关系。
2.
注意充分利用条件!