现在终于明白,为什么老师原来说过一个公司的员工能把二分写出来的员工寥寥无几了,沉迷二分,做了一阵子题慢慢总结出来了一些大体的模板,除了模板之外,最重要的还要考虑一堆边界情况,还要进行一些特判。 都觉得二分简单,其实不然,有可能你做题的时候对了就跳过去了,其实有可能是因为凑巧对了,我渐渐地发现了特判的重要还有模板的选择有事也。。。。
四种模板适用于不同的场景,但是有时候选择错的话,也会有这样或者那样的问题。
在模板之前先声明:
-
看见最值中求最值就是二分 , 最大值最小, 最小值最大等等。 — 本蒟蒻所言
-
不是有序才用二分,有序只是二分的必要条件,所以对于区间求最值,都可以尝试使用二分。---- 本蒟蒻所言
- 学会了模板之后,其它的就是根据题目进行 逻辑判断了,也就是check()函数的作用了。
四种二分模板
前两种模板:都是 while(l < r) ,但是会有区别的,区别在代码注释中有体现。
后两种模板:都是 while(l <= r), 也是在返回上有区别。
模板1: 找最大值中的最小
int main()
{
int l;
int r;
while(l < r)
{
int mid = (l + r)/ 2;
if(check())
{
r = mid; // 这里是 r = mid, 说明[l,mid]是合法范围
}
else
{
l = mid + 1; // [l,mid]这个范围都不是合法范围,所以下一次查找直接从 l = mid + 1开始了
}
//最后的l,r是答案 因为 l == r ,最终就是答案。
}
}
模板说明:
如果中间的check()函数 是target,查找的话,那么就要注意了
-
但是有可能不存在,需要特判一下是否存在. 比如 {1,4,5} 找3, 就有可能不存在,就会找到最后一个位置。
也就是r一直不动,l最终到达l == r, 所以 最终根据题意决定是否要特判一下。 -
但是也会存在这个问题 {1,2,2,2,2,4,5} 找2,看模板标题,找到最大值中的最小值.
也可以理解为找到一段target中最小的位置,所以这里会返回下标为1的位置的target
模板2:找最小值中的最大
int main()
{
int l;
int r;
while(l < r)
{
int mid = (l + r + 1)/ 2; // 这里要 l + r +1 要不然会死循环
if(check())
{
l = mid; // mid这个位置 满足条件之后 查找 [mid , right]的位置, 所以l移到mid的位置
}
else
{
r = mid - 1; // [mid,r] 不满足条件, 所以要移到满足条件的一方, r = mid - 1
}
}
//最后的l,r是答案 因为 l == r
}
模板说明:
如果中间的check()函数 是target,查找的话,那么就要注意了 瞧瞧这完美的复制粘贴
-
但是有可能不存在,需要特判一下是否存在. 比如 {1,4,5} 找3, 就有可能不存在,就会找到第一个位置(0)。
也就是l一直不动,r最终到达l == r = 0, 所以 最终根据题意决定是否要特判一下。 -
但是也会存在这个问题 {1,2,2,2,2,4,5} 找2,看模板标题,找到最小值中的最大值。
也可以理解为找到一段target中最大的位置,所以这里会返回下标为4的位置的target。
模板3: 找最值都可以
类型1: 找最小值最终返回r
int main()
{
int l;
int r;
while(l <= r)
{
int mid = (l + r )/ 2;
if(check())
{
l = mid - 1;
}
else
{
r = mid + 1;
}
}
//最终 l == r + 1, 找最小值返回r,返回r位置
}
类型2: 找最大值最终返回l
int main()
{
int l;
int r;
while(l <= r)
{
int mid = (l + r )/ 2;
if(check())
{
l = mid - 1;
}
else
{
r = mid + 1;
}
}
//最终 l == r + 1, 找最大值返回r
}
模板4: 中间保留保存最佳值
int main()
{
int l;
int r;
while(l <= r)
{
int mid = (l + r )/ 2;
if(check())
{
ans = mid; // 这里需要保证 check()函数是找最佳值的位置的。
// 这个位置已经满足一个位置了,再往mid - 1位置找看是否还有更佳位置
l = mid - 1;
}
else
{
r = mid + 1;
}
}
//答案是ans
}