题目大意:收集最少的人,让这些人能够覆盖 所有的技能。
思路:状态压缩 + 动态规划dp (状态压缩只是一个概念,就是考虑如何用1个数字去表示一个状态,方便我们将这个数字作为dp数组的下标。)
举例:比如说 全部技能有:{'java','csharp'} ,对应数组的下标是{0,1} ;
1. 先说为什么状态压缩,如何状态压缩:
因为题目的技能数目<=16,这很重要,要对数值大小很敏感,因为(1<<16)=1024*64≈70W ,完全可以用一个int容纳。故考虑用2进制表示收集到的技能的状态。
举例:根据上面的例子,那么只收集到{'java'}的状态是 (1<<0) = 1 ; 只收集到 {'csharp'}的状态是 (1<<1) = 2; 收集到 {'java','csharp'}的状态是(1<<0) + (1<<1) = 3 ;
所以收集什么技能,收集到哪一类技能,我们都可以用一个数字去表示。
只收集到{'java'}的状态是1,只收集到{'csharp'}的状态是2,收集到{'java','csharp'}的状态是 1+2 = 3;
总结:
目前我们收集技能的状态,我们可以用一个数字代表这个状态。
同理,每个人掌握技能的情况 也可以用一个数字表示。
2.状态转移
假设总人数是people_num,总技能数是 skill_num;
状态:dp[state] 表示掌握技能的状态为state时,需要的最少人数。根据上面的举例:dp[0] =1 ; dp[1] = 1; dp[3] = 2;
list[state] 表示这个状态下 人员的编号。
转移方程: dp[nextState] = min(dp[nextState],dp[ preState | p[i] ] + 1 ) ; (p[i] 是第i个人掌握技能的状态,preState | p[i] == dp[nextState] ) ;
结果要覆盖所有的技能,那么我们要求的状态就是 (1<<(skill_num)-1) ,所以最少的人数就是dp[1<<(skill_num) -1];
结果就是list[ ( 1<<skill_num ) - 1] ,人员编号的状态 随着dp的转移而转移。
3. 代码:
class Solution {
public int[] smallestSufficientTeam(String[] req_skills, List<List<String>> people) {
int plen = people.size(); //人数
int slen = req_skills.length; //总技能数
int dp[] = new int [1<<slen]; //记录状态为state时 最少需要的人数。
List<Integer> rc[] = new ArrayList[1<<slen];//记录状态为 state时,最少需要的人员的编号
Map<String,Integer> map = new HashMap<>();
for(int i=0;i<slen;i++){
map.put(req_skills[i], (1<<i));
}
for(int i=0;i<(1<<slen);i++){
rc[i] = new ArrayList<>();
dp[i] = -1;
}
//当状态为0时,也就是没有掌握技能时,需要的人数是0
dp[0] = 0;
for(int i=0;i<plen;i++){ //遍历每一个人
int sk = 0;
for(String skill : people.get(i)){//sk |= map.get(skill) ; //得到这个人掌握技能的状态
sk |= ( map.get(skill) == null ? 0 : map.get(skill) );//题目没有明确说"people掌握的技能一定存在于req_skills中",所以判断一下空值比较稳妥, 谢谢评论[快乐崇拜234]指正
}
for(int st=0;st<(1<<slen);st++){ //遍历所有的状态,看是否需要更新
if(dp[st]==-1) continue;
int newState = sk|st;
if(dp[newState]==-1 || dp[st]+1<dp[newState]){ //需要更新,那么更新
dp[newState] = dp[st] + 1; //更新人员数量
rc[newState].clear();
rc[newState].addAll(rc[st]); //更新人员的编号
rc[newState].add(i);
}
}
}
List<Integer> rsList = rc[(1<<slen)-1]; //结果就是这个 (1<<slen) -1 状态的 人员编号。
int rsSz = rsList.size();
int rsArr [] = new int[rsSz];
for(int i=0;i<rsSz;i++){
rsArr[i] = rsList.get(i);
}
return rsArr;
}
}//如果我还没有讲明白,我很抱歉
4. 反思:一开始我把题目想成了优先队列和并查集,绕了很久没做出来,
还是要对题目数据范围保持敏感,渣科练题少了要多加油了。