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 skillspeople[0],people[1], andpeople[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 <= 161 <= req_skills[i].length <= 16req_skills[i]consists of lowercase English letters.- All the strings of
req_skillsare unique. 1 <= people.length <= 600 <= people[i].length <= 161 <= people[i][j].length <= 16people[i][j]consists of lowercase English letters.- All the strings of
people[i]are unique. - Every skill in
people[i]is a skill inreq_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/
