基础算法——二分

Posted rfdragon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础算法——二分相关的知识,希望对你有一定的参考价值。

  上次我们讲了贪心,上题:

题目描述:
RFdragon摘了n种果子,每种果子堆成一堆,现在RFdragon打算将所有果子运回家,因此决定将所有果子合并成一堆。每次RFdragon可以选择任意两堆果子进行合并,消耗等同于两堆果子质量之和的力气。请你求出一个合并方案,使得RFdragon消耗的力气最少。

输入描述:
第一行一个正整数n。
第二行n个正整数,表示每堆果子的质量。

输出描述:
一个正整数,表示消耗力气的最小值。

输入样例:
5
3 2 5 2 1

输出样例:
29

其他说明:
n<100000,所有数据均在int范围内。

  今天我们要讲的是二分。设想这样一个场景:让你从1~100之间猜一个数,每次告诉你大了或者小了,要求尝试次数最少,你会猜几?想都不用想,肯定先猜50。这个时候,你已经在用二分的思想来解决问题了。二分主要分两种:二分查找和二分答案。我们先来看二分查找。我们直接来看一道题:

题目描述:
输入n个数,将n个数从小到大排序,问k排在第几。

输入描述:
第一行两个正整数n和k,中间用一个空格隔开。
第二行有n个正整数,中间有一个空格隔开。

输出描述:
一个正整数,表示k排在第几位。

输入样例:
5 7
7 3 5 6 9

输出样例:
4

其他说明:
n<1000000,所有输入数据均在int范围内,保证没有重复的数。

  它要怎么用二分来实现呢?首先,不用说,肯定要将输入的所有书进行排序。那之后呢?我们还回到刚才猜数的例子。假设你猜了50,告诉你50小了,你就排除了1~50之间的所有数,范围变为51~100;你再猜75,假如75也小了,那你又排除了51~75之间的所有数,范围变为76~100。因此,我们需要两个数来记录数据的范围。我一般喜欢用head和tail进行表示,如下:

int head=1,tail=n;

  head表示最小值,tail表示最大值。为什么tail要等于n呢?这是因为,n是数组中最大的数的下标,这个数再大,下标也不可能超过n。每次取一个中间值,表示我们“猜”的那个数。

mid=(head+tail)/2;
if(a[mid]>k)
    tail=mid-1;
else if(a[mid]<k)
    head=mid+1;
else
{
    printf("%d",mid);
    return 0;
}

  这就是我们“猜”数并缩小范围的过程。掌握了核心代码,题就不难做了。完整代码如下:

#include<cstdio>
int n,k,a[1000000],head=1,tail,mid;
int main()
{
    scanf("%d %d",&n,&k);
    tail=n;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    while(head<=tail)
    {
        mid=(head+tail)/2;
        if(a[mid]==k)
        {
            printf("%d",mid);
            return 0;
        }
        else if(a[mid]>k)
            tail=mid-1;
        else
            head=mid+1;
    }
    return 0;
} 

  这就是二分查找。那么二分答案又是什么呢?有一些题中,答案很难被直接确定,需要先确定范围,然后在范围中逐渐尝试。用一般的查找方法自然很慢,但用二分来查找答案就快的多了。为了让大家更好理解二分答案,先上一道题:

题目描述:
RFdragon家里有一些废品,由于废品实在太多了,因此需要分批运走。黑心的收废品的叔叔为了盈利,不但不给钱,反而还向RFdragon索要工钱。RFdragon好心的同意了。
RFdragon会将所有的废品分成n批运走,运走每批废品都要花一定量的钱。由于叔叔很黑心,因此向RFdragon提出以下要求:RFdragon每天给叔叔k元钱,k是一个定值,叔叔会运走几批废品,但是这几批废品必须是连续的,并且每次必须将一整批全部运走;如果运走几批废品后钱有剩余,但不够运走下一批废品,那么剩下的钱会被叔叔收入腰包。由于忍受不了废品散发的味道,RFdragon要求运走所有废品的天数不能超过m天,并且k越小越好。请你帮他求出k的最小值。

输入描述:
第一行三个正整数,中间用空格隔开,分别表示n、m。
第二行有n个正整数,表示运走每批废品的价格。

输出描述:
一个正整数,表示k的最小值。

其他说明:
所有数据<10000。

  首先,我们要确定二分范围。最小值一定是所有废品中花钱最多的那个,最大值一定是所有废品所花钱数之和。问题来了,我们每次确定一个mid值之后,如何判断这个mid值是否可以满足题意呢?我们不妨写一个check函数。

bool check(int x)//参数x表示假设k=x
{
    int cur=x,sum=1;//cur表示当前剩余钱数,sum表示运走所有废品所需天数 
    for(int i=1;i<=n;i++)
    {
        if(cur>a[i])
            cur-=a[i];//假设剩余钱数能运走当前这批废品,就让叔叔运走他 
        else
        {
            cur=x-a[i]; 
            sum++;//否则就让叔叔(使用)明天(的钱)运走他
        }
    } 
    return sum<=m;
} 

  完整代码如下:

#include<algorithm>
#include<cstdio>
using namespace std;
int n,m,a[10000];
bool check(int x)
{
    int cur=x,sum=1;
    for(int i=1;i<=n;i++)
    {
        if(cur>a[i])
            cur-=a[i];
        else
        {
            cur=x-a[i]; 
            sum++;
        }
    } 
    return sum<=m;
} 
int main()
{
    int head=0,tail=0,mid;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        tail+=a[i];
        head=max(head,a[i]);
    }
    while(head<tail)
    {
        mid=(head+tail)/2;
        if(check(mid))
            tail=mid;
        else
            head=mid+1;
    }
    printf("%d",head);
    return 0;
}

  这就是二分的用法,你学会了吗?

//答案代码 
#include<algorithm>
#include<cstdio>
using namespace std;
int n,a[10000],ans;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        a[i]+=a[i-1];
        ans+=a[i];
    }
    printf("%d",ans);
    return 0;
}

Created by RFdragon

以上是关于基础算法——二分的主要内容,如果未能解决你的问题,请参考以下文章

Java八股文面试题 基础篇 -- 二分查找算法冒泡排序选择排序插入排序希尔排序快速排序

Java八股文面试题 基础篇 -- 二分查找算法冒泡排序选择排序插入排序希尔排序快速排序

基础算法-冒泡排序与二分查找(JAVA实现)

基础算法-二分查找

大厂高频面试:Java基础篇(算法数据结构基础设计模式)

算法:二分查找(基础)