基础算法--二分查找

0 阅读4分钟

二分查找


二分查找的前提是:数组必须是有序数组。我们以在升序数组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。不断重复,直到找到目标元素。

二分算法.png

我们用一段简单的代码实现一下逻辑:

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 这四个地方,每道题可能都有差别,少有不慎

二分查找的应用(二分答案---猜答案)

确定题目是二分的题目 -> 确定答案的范围 -> 假定一个解,判断是否可行

  • 最值
  • 最小化最大值
  • 最大化最小值
  • 二分可能只是题解的其中一步

最值

我们一起来看几道例题:

www.luogu.com.cn/problem/P18…

二分算法-1.png

分析: 总结一下题意,给定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;
}

leetcode.cn/problems/nZ…

二分算法-2.png

分析: 从题意分析,最少每次吃一根,最多每次吃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

在题目中,往往会给你一个情景,求一个最大化最小值。 对最小值进行二分-->目标最小值-->反推题目给出的另一个条件

对于最小化最大值也是同理的


我们来看一道例题:

二分算法-3.png

分析: 对于最短距离要求最大值,我们知道,移走的石头数量越多,最短距离的值就可能越大。所以我们可以对最短跳跃距离进行二分,然后采用贪心的策略去尝试移走石头,看移走石头的数量是否满足题设。

#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;
}