二分查找
二分查找的前提是:数组必须是有序数组。我们以在升序数组nums中查找一个数为例: 我们首先设置双指针l和r,将l放置在数组的第一个位置,r放置在数组的最后一个位置。再设置一个辅助指针mid,先将mid放在数组的中间(即(l + r)/ 2或(l + r + 1)/ 2),此时我们比较nums[mid]和我们的目标元素target,如果target更大,则将l移到mid + 1的位置,更新mid;否则,将r移到mid - 1的位置,更新mid。不断重复,直到找到目标元素。
我们用一段简单的代码实现一下逻辑:
int bs(int a[], int b[], int tg){
int l = 0, r = n - 1;
while(l < r){
int mid = (l + r) / 2;
if(a[mid] >= tg) r = mid;
else l = mid + 1;
}
return r;
}
但是需要注意:在上面的代码中,有四个地方容易造成死循环
- l < r
- r = mid
- l = mid + 1
- return r 这四个地方,每道题可能都有差别,少有不慎
二分查找的应用(二分答案---猜答案)
确定题目是二分的题目 -> 确定答案的范围 -> 假定一个解,判断是否可行
- 最值
- 最小化最大值
- 最大化最小值
- 二分可能只是题解的其中一步
最值
我们一起来看几道例题:
分析: 总结一下题意,给定n棵树的高度,求一个最大值H,高于H的部分全部砍掉,要求得到M米或以上的木材(再高一米,就不够M米了)。 我们可以使用二分H,在check函数中去判断这个H是否可行。
#include <iostream>
#define ll long long
using namespace std;
int n, m;
int a[1000005];
bool check(int h){
ll sum = 0;
for(int i = 1; i <= n; ++i){
if(a[i] - h > 0){
sum += (a[i] - h);
}
}
if(sum >= m){
return 1;
}
else{
return 0;
}
}
int main(){
cin >> n >> m;
int l = 0, r = 0;
for(int i = 1; i <= n; ++i){
cin >> a[i];
r = max(r, a[i]);
}
int mid = 0, ans = -1;
while(l <= r){
mid = (l + r) / 2;
if(check(mid)){
ans = mid;
l = mid + 1;
}
else{
r = mid - 1;
}
}
cout << ans << endl;
return 0;
}
分析: 从题意分析,最少每次吃一根,最多每次吃max根,其中max是piles[i]中的最大值。 那么我们用1~max进行二分,用算出的时间x和H进行比较,来找出最慢的吃香蕉时间。
#include <iostream>
#include <vector>
using namespace std;
bool check(vector<int>& piles, int k, int h){
long long t = 0;
int n = piles.size();
for(int i = 0; i < n; ++i){
t += piles[i]/k;
if(piles[i]%k){
++t;
}
}
if(t <= h){
return 1;
}
else{
return 0;
}
}
int minEatingSpeed(vector<int>& piles, int h) {
int l = 1, r = 0, mid = 0;
int n = piles.size();
for(int i = 0; i < n; ++i)
{
r = max(r, piles[i]);
}
int ans = 0;
while(l <= r){
mid = (l + r) / 2;
if(check(piles, mid, h)){
ans = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
return ans;
}
int main(){
int n, h, x;
vector<int> p;
cin >> n >> h;
for(int i = 1; i <= n; ++i){
cin >> x;
p.push_back(x);
}
int ans = minEatingSpeed(p, h);
cout << ans << endl;
return 0;
}
最大化最小值&最小化最大值
我们首先来理解一下什么是“最大化最小值” 假设有6个班级,每个班级30人,我们找出每个班的最矮身高: 1班:167cm 2班:170cm 3班:165cm 4班:166cm 5班:171cm 6班:165cm 那么最大化的最小值就是171cm
在题目中,往往会给你一个情景,求一个最大化最小值。 对最小值进行二分-->目标最小值-->反推题目给出的另一个条件
对于最小化最大值也是同理的
我们来看一道例题:
分析: 对于最短距离要求最大值,我们知道,移走的石头数量越多,最短距离的值就可能越大。所以我们可以对最短跳跃距离进行二分,然后采用贪心的策略去尝试移走石头,看移走石头的数量是否满足题设。
#include <iostream>
using namespace std;
int s, n, m;
int a[500005];
bool check(int _min){//_min是两个石块之间距离的最小值
int sum = 0;
int now = 0, next = 0;
//now是当前所在石块的位置,next是下一步跳到的石块的位置
while(next < n){
++next;
if(a[next] - a[now] < _min){
++sum;
}
else{
now = next;
}
}
if(sum <= m){
return 1;
}
else{
return 0;
}
}
int main(){
cin >> s >> n >> m;
for(int i = 1; i <= n; ++i){
cin >> a[i];
}
a[n + 1] = s;//放入终点的石块
++n;
int l = 1, r = s, mid = 0, ans = 0;
while(l <= r){
mid = (l + r) / 2;
if(check(mid)){
ans = mid;
l = mid + 1;
}
else{
r = mid - 1;
}
}
cout << ans << endl;
return 0;
}