[LeetCode] 1125. Smallest Sufficient Team 最小的必要团队


In a project, you have a list of required skills req_skills, and a list of people. The ith person people[i] contains a list of skills that the person has.

Consider a sufficient team: a set of people such that for every required skill in req_skills, there is at least one person in the team who has that skill. We can represent these teams by the index of each person.

  • For example, team = [0, 1, 3] represents the people with skills people[0]people[1], and people[3].

Return any sufficient team of the smallest possible size, represented by the index of each person. You may return the answer in any order.

It is guaranteed an answer exists.

Example 1:

Input: req_skills = ["java","nodejs","reactjs"], people = [["java"],["nodejs"],["nodejs","reactjs"]]
Output: [0,2]

Example 2:

Input: req_skills = ["algorithms","math","java","reactjs","csharp","aws"], people = [["algorithms","math","java"],["algorithms","math","reactjs"],["java","csharp","aws"],["reactjs","csharp"],["csharp","math"],["aws","java"]]
Output: [1,2]

Constraints:

  • 1 <= req_skills.length <= 16
  • 1 <= req_skills[i].length <= 16
  • req_skills[i] consists of lowercase English letters.
  • All the strings of req_skills are unique.
  • 1 <= people.length <= 60
  • 0 <= people[i].length <= 16
  • 1 <= people[i][j].length <= 16
  • people[i][j] consists of lowercase English letters.
  • All the strings of people[i] are unique.
  • Every skill in people[i] is a skill in req_skills.
  • It is guaranteed a sufficient team exists.

这道题给了一个技能数组,是完成某一个项目所需要的必备技能。又给了一个候选人的数组,每个人都有不同的技能,现在问最少需要多少人可以完成这个项目。由于每个人的技能点不同,为了能完成这个项目,所选的人的技能点的并集要正好包含所有的项目必备技能,而且还要求人数尽可能的少,这就是一道典型的动态规划 Dynamic Programming 的题。这道题敢标 Hard 是有其一定的道理的,首先 DP 数组的定义就是一个难点,因为我们也不知道最少需要多少个人可以拥有所有的必备技能。另一个难点是如何表示这些技能,总不能每次都跟 req_skills 数组一一对比吧,太不高效了。一个比较好的方法是使用二进制来表示,有多少个技能就对应多少位,某人拥有某技能,则对应位上为1,否则为0。若总共有n个必备技能,实际上只用一个 2^n-1 的数字就可以表示了。这里我们的 dp 数组定义为 HashMap,建立技能集合的位表示数和拥有这些技能的人(最少的人数)的集合之间的映射,那么最终的结果就是 dp[(1<<n)-1] 对应的数组的长度了。首先将 dp[0] 映射为空数组,因为0表示没有任何技能,自然也不需要任何人,这个初始化是一定要做的,之后会讲原因。这里再用另一个 HashMap,将每个技能映射到其在技能数组中的坐标,这样方便之后快速的翻转技能集合二进制的对应位。先用一个 for 循环来建立这个 skillMap 的映射,然后就是遍历每个候选人了,使用一个整型变量 skill,然后根据 skillMap 查找这个人所有的技能,并将其对应位翻为1,这样此时的 skill 就 encode 了该人的所有的技能。现在就该尝试更新 dp 了,遍历此时 dp 的所有映射,此时之前加入的那个初始化的映射就发挥作用了,就像很多其他 DP 的题都要给 dp[0] 初始化一样,没有这个引子,后面的更新都不会发生,整个 for 都进不去。将当前的 key 值或上 skill,则表示将当前这个人加到了某个映射的人的集合中了,这样就可能会生出现一个新的技能集合的位表示数(也可能不出现,即当前这个人的所有技能已经被之前集合中的所有人包括了),此时看若 dp 中不存在这个技能集合的位表示数,或者新的技能集合的位表示数对应的人的集合长度大于原来的人的集合长度加1,说明 dp 需要被更新了,将新的位表示数映射到加入这个人后的新的人的集合,这样更新下来,就能保证最终 dp[(1<<n)-1] 的值最小,因为题目中说了一定会有解,参见代码如下:


class Solution {
public:
    vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
        int n = req_skills.size();
        unordered_map<int, vector<int>> dp(1 << n);
        dp[0] = {};
        unordered_map<string, int> skillMap;
        for (int i = 0; i < n; ++i) {
            skillMap[req_skills[i]] = i;
        }
        for (int i = 0; i < people.size(); ++i) {
            int skill = 0;
            for (string str : people[i]) {
                skill |= 1 << skillMap[str];
            }
            for (auto a : dp) {
                int cur = a.first | skill;
                if (!dp.count(cur) || dp[cur].size() > 1 + dp[a.first].size()) {
                    dp[cur] = a.second;
                    dp[cur].push_back(i);
                }
            }
        }
        return dp[(1 << n) - 1];
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1125


参考资料:

https://leetcode.com/problems/smallest-sufficient-team/

https://leetcode.com/problems/smallest-sufficient-team/discuss/334832/c%2B%2B-dp-bitmask-solution-with-algorithm

https://leetcode.com/problems/smallest-sufficient-team/discuss/334669/Java-DFS-search-with-Pruning-using-Bitwise-tricks-4ms-beating-98


LeetCode All in One 题目讲解汇总(持续更新中...)

posted @ 2021-05-19 11:37  Grandyang  阅读(719)  评论(0编辑  收藏  举报
Fork me on GitHub