本文主要是介绍二分的姿势的选取,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
解的范围为实数
精度判断
这样做是最基础的方法,但是不是很推荐。会存在浮点误差。
left = 0.0, right = 0x3f3f3f3f;
while (dcmp(right - left) != 0){ mid = (right + left) / 2.0; if (judge(mid)) right = mid; else left = mid; }
次数判断
比较推荐这种写法。
直接指定循环次数(100次)作为终止条件。1次循环可以把区间的范围缩小一半,100次的循环则可以达到 2−100≈10−30 的精度范围。或者,也可以根据需要的精度算出需要二分的次数。
cnt = 100
left = 0.0, right = 0x3f3f3f3f;
while (cnt --){ mid = (right + left) / 2.0; if (judge(mid)) right = mid; else left = mid; }
解的范围是整数
知乎上用排列组合做了一波64种姿势,回答地址。
不说他分析的正确性。这儿就讲四种。
最大化最小值
此种题目一般是“二分值越小越容易满足条件”,然后求符合条件的最大值。比如,lower_bound函数。
区间长度为1时的写法:
左闭右闭形式,解的范围为 [lb,ub] 。
// 计算区间为[lb, ub]
while( lb < ub ) // 区间长度为1时终止循环
{int mid = lb + (ub - lb + 1) / 2; // 由于是区间长度为1时终止循环,所以要加1if( ok(m) ) lb = mid;else ub = mid - 1;
}
//
跳出循环时 lb == rb
区间长度为2时的写法:
左闭右开形式,解的范围为 [lb,ub) 。
int lb = 1, ub = N+1; // 注意,从初始值ub就是取开区间了
while(lb + 1< ub){ // 区间长度为2时终止循环int mid = (ub + lb) / 2; if(judge(mid)) lb = mid; else ub = mid;
}
// 跳出循环时 lb + 1 == ub
// 答案为 lb
最小化最大值
此种题目一般是“二分值越大越容易满足条件”,然后求符合条件的最小值。如,upper_bound函数。
区间长度为1时的写法:
左闭右闭形式,解的范围为 [lb,ub] 。
while( lb < ub )
{int m = lb + (ub - lb) / 2; // 这里虽然区间长度为1,但不需要加1(不会死循环)if( ok(m) ) ub = m;else lb = m + 1;
}
// 跳出循环时 lb == ub
区间长度为2时的写法:
左开右闭形式,解的范围为 (lb,ub] 。
int lb = 0, ub = N; //注意,从初始值开始,左侧就取的开区间
while(lb + 1< ub){ int mid = (ub + lb) / 2; if(judge(mid)) ub = mid; else lb = mid;
}
// 跳出循环时 lb + 1 == ub
// 答案为 ub
但是在所有情况下,上面的几种形式都是等价的呢? 答案是不是的。
结论:上述形式的选取和judge对应的函数的单调性有关。如果judge函数是单调递增的,应该选取左开右闭形式。反之,如果judge函数是单调递减的,应该选取左闭右开的形式。
关于judge函数的单调性,比如,对于一个递增的数组,下标位置越大的数,值越大,越不可能是最终的答案。所以这个时候judge就是递减的。
例题
Leetcode中的二分
这篇关于二分的姿势的选取的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!