牛客网-E-分组

题目来源:牛客

题目描述:
链接:登录—专业IT笔试面试备考平台_牛客网
dd当上了宣传委员,开始组织迎新晚会,已知班里有n个同学,每个同学有且仅有一个擅长的声部,把同学们分成恰好m组,为了不搞砸节目,每一组里的同学都必须擅长同一个声部,当然,不同组同学擅长同一个声部的情况是可以出现的,毕竟一个声部也可以分成好几个part进行表演,但是他不希望出现任何一组的人过多,否则可能会导致场地分配不协调,也就是说,她希望人数最多的小组的人尽可能少,除此之外,对组内人员分配没有其他要求,她希望你告诉她,这个值是多少,如果无法顺利安排,请输出-1

解题思路

要做出这道题,首先我们要知道这道题是什么意思, 我们就用示例1来讲解,首先是输入n和m,表示有多少人和希望分成多少组,每个人都有擅长的声部,也就是第二行数字,这里就直接理解为每个人都有一个数字,我们最终要得到的分组,希望每个组里面数字都是相同的,比如第一组里面全是2,而第二组里面全是3,不可以出现一个组里既有2又有3,另外,不同的组里数字也可以相同,比如第一组的数字是2,第二组的数字同样也可以是2,在这个分组的前提下,我们要输出给定的组中最多人数组的最小值,什么意思?这里的2,2,3,3,3,一共5个人,要分3组,我们可以这样分组{2},{2},{3,3,3},分为3组,也可以{2,2},{3},{3,3}分组,两种分组方法中,第二种方法的组里最多人数是2,比第一种的少(第一种把3个3放到一块,组内最大人数为3),所以这道题的答案就是2,这是最优解

明白了题是什么意思,下面我们来做题,我们首先要对数据进行处理,这里我们自然想到了hash,所以我们使用unordered_map

第一步,我们的是暴力求解,但是仔细想想的话是不行的,暴力求解太过麻烦,我们不知道每一组应该分多少个人,所以我们逆向思维,我们先给出组内最大人数,也就是题目要求的答案,然后我们判断这个答案行不行,我们假设这个人数为3,也就是说,我们一个组里最多只能有3个人,这里我们再假设擅长a声部的人有8个,所以擅长a声部的人应该分为3组,这里计算组数为n =(a/x)+(a/x==0?0:1),看看先可以分为几组,再看看有没有余数,就是擅长a声部的人可以分为多少组,我们再计算出其他声部的分组有多少组,我们再把这些组数全部加起来,如果等于m,那么就是正确的,否则就错误,我们分组的最终结果,每个小组最小肯定有一个人,最大应该为每一个声部中最大的值,也就是hash表里的最大值,这里我们用hmax表示,所以我们分组的人数应该为一个区间 [ 1 , hmax ] ,我们从小到大对x进行判断,从1开始,只要x符合,那答案就是这个x,我们这里要封装一个函数check,check里应该计算出最终答案为x时的分组数总共为多少,如果等于m则正确

 算法优化

暴力算法的时间复杂度太大,我们需要对他进行优化,这道题应该选用二分,下面我们来看为什么

我们知道分组的人数应该在区间 [ 1 , hmax ] 中,假设我们的最终结果为ret,在他们的中间,此时就分为了  [ 1 , ret-1 ] , ret , [ ret+1 , hmax ] ,在1到ret-1中,check计算出的组数都是大于m的,在ret+1到hmax中,计算出的组数都是小于m的,想一想,如果最终人数少了,那么组数自然就多,人数多了,组数自然就少了,所以都不会等于m,优化时,我们 if (check(mid) <= m) ,此时这个点是有可能为正确点,我们让r = mid,否则,l = mid+1,最终结果就是r或者l

代码(暴力求解)

#include<iostream>
#include<unordered_map>
using namespace std;

int n,m;
unordered_map<int,int> cnt;

bool check(int x)//最多人数为x时是否可以分为m组
{
    int g=0;//可以分为多少组
    for(auto&[a,b]:cnt)//存的pair,a为声部的类型,b为声部的人数
    {
        g += b/x + (b%x==0?0:1);//b/x看看能分多少组,再看看有没有余数
    }
    return g<=m;
}
int main()
{
    cin>>n>>m;
    int hmax = 0;//用来记录声部中最多的人数
    for(int i = 0;i<n;i++)
    {
        int x;
        cin>>x;
        hmax = max(hmax,++cnt[x]);
    }
    int kinds=cnt.size();
    if(kinds>m)//处理边界情况
    {
        cout<<-1<<endl;
    }else{
        for(int i =1;i<=hmax;i++)//枚举所有的最多人数
        {
            if(check(i))
            {
                cout<<i<<endl;
                break;
            }
        }
    }
}

首先是暴力求解的代码,大家结合注释和思路就可以看懂

代码(二分)

#include<iostream>
#include<unordered_map>
using namespace std;

int n,m;
unordered_map<int,int> cnt;

bool check(int x)//最多人数为x时是否可以分为m组
{
    int g=0;//可以分为多少组
    for(auto&[a,b]:cnt)//存的pair,a为声部的类型,b为声部的人数
    {
        g += b/x + (b%x==0?0:1);//b/x看看能分多少组,再看看有没有余数
    }
    return g<=m;
}
int main()
{
    cin>>n>>m;
    int hmax = 0;//用来记录声部中最多的人数
    for(int i = 0;i<n;i++)
    {
        int x;
        cin>>x;
        hmax = max(hmax,++cnt[x]);
    }
    int kinds=cnt.size();
    if(kinds>m)//处理边界情况
    {
        cout<<-1<<endl;
    }else{
        int l = 1,r=hmax;
        while(l<r)
        {
            int mid = (l+r)/2;
            if(check(mid))
                r=mid;
            else
                l = mid+1;
        }
        cout<<l<<endl;//这里l或者r都可以
    }
}

这里同理,只是使用了二分的思想来更快的寻找结果

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值