什么时候用二分答案
题目要求一个解,通常是最优解,比如求满足某个条件下的最大值或最小值。
当求解比较困难时,可以思考是否能转换为判定问题,即给定一个解,判定它是否满足该条件。通常情况下,判定逻辑清晰明了,但带来的问题是需要枚举整个解空间,复杂度为
O
(
n
)
O(n)
O(n)。如果发现解空间具有单调性,就可以将解空间一分为二,每次缩小
1
/
2
1/2
1/2,从而将时间复杂度从
O
(
n
)
O(n)
O(n)降到
O
(
l
o
g
n
)
O(logn)
O(logn)。这就是通常说的"二分+判定",是复杂题目里进行算法优化的常用方法。
几个关键点
判定算法/函数:
给定一个解
x
x
x,判定是否满足题目的条件。一般判定算法会用到贪心,复杂度控制在
O
(
n
)
O(n)
O(n)。
解空间具有单调性:
如果判定函数
c
h
e
c
k
(
x
)
check(x)
check(x)关于
x
x
x具有单调性,以 力扣1011为例,当运载能力
x
x
x
≥
X
\ge X
≥X能运完,
<
X
<X
<X运不完,我们就说解空间具有单调性。
判定问题的转化:判定是否满足题目的条件等价于写出满足题意的不等式。
二分:
当解空间具有单调性时,枚举
x
x
x时就可以用二分了。
例题
力扣1011
直接求解无从下手,转换为判定问题:当运载能力为
m
i
d
mid
mid时,是否能在days天内将货物运完。
解空间是否单调性:当
x
≥
m
i
d
x\ge mid
x≥mid时,肯定能运完,当
x
<
m
i
d
x<mid
x<mid时,肯定运不完,因此解空间单调。
1.写出判定函数:
bool check(vector<int>& weights, int days, int cap) {
int current=0;
int day=1;
for(auto weight:weights) {
if(weight+current<=cap) {
current+=weight;
}else {
day++;
current=weight;
}
}
return day<=days;//判定是否满足题目的条件等价于写出满足题意的不等式
}
- 套用二分模板
2.1 将判定函数放到 i f if if条件判断.
2.2 根据是求最大值还是最小值, i f if if里修改 l e f t left left或 r i g h t right right:
最大值/前驱 ≤ \le ≤: l e f t = m i d left=mid left=mid
最小值/后继 ≥ \ge ≥: r i g h t = m i d right=mid right=mid
2.3 e l s e else else里写另一半:
r i g h t = m i d + 1 right=mid+1 right=mid+1
l e f t = m i d − 1 left=mid-1 left=mid−1 //这种情况下 m i d = ( l e f t + r i g h t + 1 ) / 2 mid=(left+right+1)/2 mid=(left+right+1)/2
int shipWithinDays(vector<int>& weights, int days) {
int left=0,right=0;
for(auto weight:weights) {
left=max(left, weight);
right+=weight;
}
while(left<right) {
int mid=(left+right)/2;
if(check(weights, days, mid)) { //最小值/后继
right=mid;
} else {
left=mid+1;
}
}
return right;
}
- 无解情况可以加哨兵,如前驱时 l e f t = − 1 left=-1 left=−1表示无解,后继时 r i g h t = n right=n right=n表示无解