二分法(算法)

定义:二分查找算法也称折半搜索算法,对数搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半,时间复杂度是log(n)

  • 一是有序数组(这里可能是整体有序比如[1,2,3,4,5],也有可能是局部有序比如[4,5,1,2,3]),
  • 二是特定元素(也有可能是满足特定的条件)。由定义我们大概就知道了二分法的应用场景,在有序数组中找特定值都可以考虑用二分法

二分法的步骤

我们要确定一个区间[L,R]我们要找到一个性质(由题目条件决定),并且该性质满足一下两点: ①满足二段性 ②答案是二段性的分界点

注:由答案和数据的关系进行二分查找的算法是最常见的

我们要在一组升序的数组找一个数的下标,那我们肯定是先拿中间的与他进行比较,比较大小的判断,其实就相当于是这个性质,且这个性质满足二段性,将大于和小于我们要查找的值分为两段,而我们的查找结果就是分界点

有二段性的答案就是满足二分查找法的重要标志之一

所谓二段性,就是数组一定有一段左侧区域严格满足某一性质(即is_blue返回true),右侧严格满足另一性质,这两种性质必须是严格互斥的,这两段中间即为二段性边界。

二分法的使用条件:

①上下界确定 ②区间内有序(也可以是局部有序)

二分法的目的:

二分查找用于在多条记录中快速找到待查找的记录,它的思想是:每次将查找的范围缩小一半,直到最后找到记录或者找不到记录返回

标准解题模板:
 

def check():
    **# check为题目中判断条件**
    pass
 
def bin_search(a, n, x):
    l, r = -1, n + 1
    while l + 1 != r:
        mid = (l+r) // 2
        if check(mid):
            l = mid
        else:
            r = mid
    return (l or r) # 选取需要的部分进行返回

模板分析:

左右指针均指向数组外,这样移动时直接使 *l*,r=mid即可——*相当于自动完成了+1和-1的操作*
返回左边界,则当≤目标值时移动 *l* ; 返回右边界,则当≥目标值时移动 r
终止条件为r=*l*+1 ——*这里相当于左闭右开,即此时区间内只有left对应的元素*

下面给大家带来两个例题供大家学习和参考

例题一:可凑成的最大花束数

思路:利用答案二分是最常考的一种方式,假设可以打包成每组x个花束,那么我们就能得知x*k<=sum的,我们只需要将所有的花相加得到sum,再判断其和x,k的关系即可,若是x*k<=sum,则表示x还能取得更多,更新l=mid,若是x*k>=sum,表示不能够满足,更新r=mid。

代码实现

#include <iostream>
using namespace std;
using ll = long long;
const int N = 200001;
ll n,k,a[N];

bool check(ll x){
  //检查x*k和res的关系
  ll res=0;
  for(int i=1;i<=n;i++){
    res+=min(a[i],x);
  }
  return res/k>=x;
}

int main()
{
  ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>k;
  ll sum=0;
  for(int i=1;i<=n;i++){
    cin>>a[i];sum+=a[i];
  }
  ll l=0,r=sum+1;
  ll mid=0;
  while((l+1)!=r){
    mid=(r+l)>>1;
    if(check(mid)){
      l=mid;
    }else{
      r=mid;
    }
  }
  cout<<l;
  return 0;
}

例题二:最大通过数

思路:单纯用贪心算法无法同时考虑两个数组,观察发现我们只需要知道左入口通关的任务数x和右入口通关的任务数y,寻找min(x+y)即可,循环一次就加一次耗费时间太长,由于只要计算可以到达的最大卡关之和不需要考虑具体的xy,所以我们可以通过前缀和求x和y,再通过定x用二分法求y的方式进行查找最优x+y。

代码实现:

#include <iostream>
using namespace std;
using ll = long long;
const int N = 200001;
ll n,m,k,a[N],b[N];

bool check(ll x){
  //x代表一共通过的任务
  ll res=1e10+1; //表示可以通过的最小的能源
  int j=0;//右入口
  for(int i=0;i<=n;i++){
    j=x-i;
    if(j<0)break;
    if(j>m)continue;
    else res=min(res,(a[i]+b[j]));
  }
  if(res>k)return true;
  else{
    return false;
  }
}

int main()
{
  ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>m>>k;
  for(int i=1;i<=n;i++){
    cin>>a[i];a[i]+=a[i-1];
  }
  for(int i=1;i<=m;i++){
    cin>>b[i];b[i]+=b[i-1];
  }
  ll l=0,r=n+m+1;
  while(l+1<r){
    ll mid=(r+l)>>1;
    if(check(mid)){
      r=mid;
    }else{
      l=mid;
    }
  }
  cout<<l;
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值