leetcode5130 最小的必要团队

题目大意:收集最少的人,让这些人能够覆盖 所有的技能。

思路:状态压缩 + 动态规划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. 反思:一开始我把题目想成了优先队列和并查集,绕了很久没做出来,

还是要对题目数据范围保持敏感,渣科练题少了要多加油了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值