题目来源:牛客
题目描述:
链接:登录—专业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都可以
}
}
这里同理,只是使用了二分的思想来更快的寻找结果