注:二分法只适用于排好序的有序的数列,本篇以从小(左)到大(右)为唯一顺序进行讲解
注:二分答案不同于二分查找,如果你想要学习二分查找,那么这篇文章将对你帮助不大,本文主讲二分答案
个人观点:我认为二分答案与二分查找的区别在于,二分查找主要是寻找某元素是否存在,以找到为算;而二分答案应用较多的是不仅要找到,更要找最优,比如查找一个最大或者最小符合条件的值,那么这一定是一个二分答案题,通常来说,二分答案常见于竞赛题目中,所以竞赛所讲、所用的“二分法”多半是二分答案(法)
本方法使用范围/条件:可概述为求符合条件的最小值;由此观之,存在临界最小值满足条件,当大于等于临界值时,都是符合条件的,例如
(本例有些极端,数据偏小,数据量大的时候二分优势明显,这里仅作为例子)
例如:a[10] = {1, 8, 12, 17, 21, 25, 26, 29, 35, 45},求大于15的最小值
显然,答案为17
当我们用二分的时候,这里就有两种情况:
一种是中轴(即mid轴)对应的数值不在范围内,比如上例中,假如mid = 2, a[mid] = 12 < 17
另一种则是中轴对应的数值在范围内,即中轴在满足条件的范围内,如上例,假如mid = 3(a[mid] = 17)或者mid = 4(a[mid] = 21),都满足a[mid] > 17
想要得到正解17,还需要功夫。操作思路如下:
针对上述第一种情况,mid不在范围内说明mid小了,此时就应该放弃查找mid左边的数据,于是更新左端点,使左端点移动至mid位置
针对上述第二种情况, mid在范围内说明满足第一个条件,但是是不是最小那就不知道,这时就不用管它,假装这个mid就是最小,但这还不为结束,因为只是假装,没有验证,只有当左端点不断移动,等于或超过右端点时,就基本确定了,这个mid就是满足条件最小值——因为mid左边不满足第一个条件,而左端于右端重合了,说明该mid对应数值就是临界
端点的移动必然引起mid轴移动,
情况一:上一次mid轴不在范围内,左端点移动至上一次的mid轴,使本次mid轴向右靠近范围;
情况二:上一次mid轴在范围内,右端移动至上一次的mid轴,使本次mid轴向左靠近范围。
为了避免死循环出现,对于情况一,mid轴部长范围了,那么左端点就不必移动至mid处,而是直接移动至mid + 1 处,这样做最大的原因出在C语言整数除法的特性上,比如(1 + 2) / 2 = 1
代码如下:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = (l + r) / 2;
if (judge(mid)) r = mid;//judge()函数判断是否在范围内,为布尔型
else l = mid + 1;//避免死循环
}
return l;
}
右端思路同左端,操作细节略有不同,直接给出代码(既然是模板,就没有什么好解释的了,记下来就完了),代码如下:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = (l + r + 1) / 2;//+1避免死循环
if (judge(mid)) l = mid;
else r = mid - 1;
}
return 1;
}
以下问题全部有视频讲解,建议步骤:先看原题,思考做题,视频讲解,代码研读
题目一:数的范围
思路 && 代码如下:
#include
#include
#include
using namespace std;
const int N = 100010;
int arry[N];
int n, q, k;
int main(void)
{
scanf("%d %d", &n, &q);
for (int i = 0; i < n; ++ i)
scanf("%d", &arry[i]);
while (q --)
{
int l = 0, r = n - 1;
scanf("%d", &k);
while (l < r)//套模板,先找左端
{
int mid = (l + r) / 2;
if (arry[mid] >= k) r = mid;
else l = mid + 1;
}
if (arry[ed] == k)
{
cout << r << ' ';
r = n - 1;
while (l < r)//再找右端
{
int mid = (l + r + 1) / 2;
if (arry[mid] <= k) l = mid;
else r = mid - 1;
}
cout << r << endl;
}
else
cout << "-1 -1" << endl;
}
return 0;
题目二:数的三次方根
思路 && 代码如下:
#include
#include
using namespace std;
int main(void)
{
double n, l, r, mid;
l = -10000, r = 10000;
cin >> n;
while (r - l > 1e-8)//当左端l与右端精度差极小时,可认为该值即为三次方根
{
mid = (l + r) / 2;
if (mid * mid * mid >= n)
r = mid;
else
l = mid;
/*因为是浮点数,不必考虑死循环,每次二分得到的中轴一定不
同,不存在(1 + 2) / 2 = 1的情况*/
}
printf("%lf", mid);
return 0;
}
题目三:机器人跳跃问题
思路 && 代码如下:
#include
#include
#include
using namespace std;
const int N = 100010;
int n, maxu, a[N];
bool judge(int x)
{
for (int i = 0; i < n; ++ i)
{
x = 2 * x - a[i+1];
if (x > maxu) break;
if (x < 0) return false;
}
return true;
}
int main(void)
{
cin >> n;
for (int i = 1; i <= n; ++ i)
{
scanf("%d", &a[i]);
if (maxu < a[i]) maxu = a[i];
}
int l = 0, r = N;
while (l < r)//经典二分
{
int mid = (l + r) / 2;
if (judge(mid)) r = mid;
else
l = mid + 1;
}
cout << r << endl;
return 0;
}
题目四:分巧克力
视频讲解
思路 && 代码如下:
#include
#include
#include
#include
#include
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N];
bool judge(int x)
{
int sum = 0;
for (int i = 0; i < n; ++ i)
{
sum = sum + (h[i] / x) * (w[i] / x);
if (sum >= m) return true;
}
return false;
}
int main(void)
{
scanf("%d %d", &n, &m);
for (int i = 0; i < n; ++ i)
scanf("%d %d", &h[i], &w[i]);
int s = 1, t = N;
while (s < t)
{
int mid = (s + t + 1) / 2;
if (judge(mid)) s = mid;
else t = mid - 1;
}
printf("%d", s);
return 0;
}
本文节选题目全部出自AcWing,如果想要一起学习的伙伴,可以一起学习,点击链接,愿与大家一起进步!如有错误,敬请指正;如有侵权,立即删除!谢谢大家的关心、鼓励与支持!