题面:
作为项目经理,你规划了一份需求的技能清单 req_skills,并打算从备选人员名单 people 中选出些人组成一个「必要团队」( 编号为 i 的备选人员 people[i] 含有一份该备选人员掌握的技能列表)。
所谓「必要团队」,就是在这个团队中,对于所需求的技能列表 req_skills 中列出的每项技能,团队中至少有一名成员已经掌握。
我们可以用每个人的编号来表示团队中的成员:例如,团队 team = [0, 1, 3] 表示掌握技能分别为 people[0],people[1],和 people[3] 的备选人员。
请你返回 任一 规模最小的必要团队,团队成员用人员编号表示。你可以按任意顺序返回答案,本题保证答案存在。
示例 1:
输入:req_skills = ["java","nodejs","reactjs"], people = [["java"],["nodejs"],["nodejs","reactjs"]]
输出:[0,2]
示例 2:
输入:req_skills = ["algorithms","math","java","reactjs","csharp","aws"], people = [["algorithms","math","java"],["algorithms","math","reactjs"],["java","csharp","aws"],["reactjs","csharp"],["csharp","math"],["aws","java"]]
输出:[1,2]
提示:
1 <= req_skills.length <= 16
1 <= people.length <= 60
1 <= people[i].length, req_skills[i].length, people[i][j].length <= 16
req_skills 和 people[i] 中的元素分别各不相同
req_skills[i][j], people[i][j][k] 都由小写英文字母组成
本题保证「必要团队」一定存在
题解:
这题是一个01背包的变种,求最小值且要记录路径;
用二进制数来表示每个人掌握的技能。
f[j]表示满足j对应的所有技能最少的人数,初始时,f[0]=0,其余为正无穷;
与01背包一样,第一重循环枚举人,第二重循环从(1<<m)-1倒叙枚举到0,每次更新f[j|p[i]]=min(f[j|p[i]],f[j]+1),最后的个数f[(1<<m)-1];
每次转移的时候记录更新的过程,如果发生了min,记录g[j|p[i]]=make_pair(i,j),表示这个地方是从第i个人和状态j转移过去的,答案可以倒叙迭代,从j=(1<<m)-1到j==0,每次加入
g.first作为答案,然后j=g[j].second;
时间复杂度为n*2^m;空间复杂度0(n+2^m)
代码:
class Solution { public: vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) { const int maxn=10000; unordered_map<string,int>skills; int m=0; for(auto s:req_skills) skills[s]=m++; int n=people.size(); vector<int>p(n,0); for(int i=0;i<n;i++) for(auto s:people[i]) p[i]=p[i]|(1<<skills[s]); vector<int>f(1<<m,maxn); vector<pair<int,int> >g(1<<m); f[0]=0; g[0]=make_pair(-1,-1); for(int i=0;i<n;i++) for(int j=(1<<m)-1;j>=0;j--) if(f[j|p[i]]>f[j]+1) { f[j|p[i]]=f[j]+1; g[j|p[i]]=make_pair(i,j); } vector<int>ans; for(int j=(1<<m)-1;j;j=g[j].second) ans.push_back(g[j].first); return ans; } };