1125. 最小的必要团队
题目链接:1125. 最小的必要团队
方法:状态压缩 + 0,1背包
解题思路
- 状态压缩:由题目可知,\(req_skills\) 存放的是所有技能的集合,每个人会其中的某几项。若将 \(req_skills\) 中的每个技能对应二进制上的一位 \(1\),可以得到一个标志 \(target\),那么每个人也将得到一个表示其所会技能的标志 \(mask\);
- 问题:从 \(people\) 中选择一些人(物品),使得其 \(mask\) 的并集为 \(target\),同时使得选择的人数最少;
- \(dp[i][j]\) 表示从前 \(i\) 个人中选,集合的并集为 \(j\) 的所有选择方式;
- 其数值表示所有选择方式中人数的最小值,由于题目要给出选择方案,那么将其数值变为存储一个二进制数,某位为 \(1\) 表示选择了该人;
- 此时 \(dp\) 数组应该初始化为最大值,即所有人都选;
- 即 0,1 背包变形,可以进行相应优化。
代码
class Solution {
public:
vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
unordered_map<string, int> idx;
int m = req_skills.size(), target = (1 << m) - 1;
for (int i = 0; i < m; i ++ ) idx[req_skills[i]] = i; // 记录技能下标
int n = people.size(); // 人数
vector<int> skill(n); // skill[i]表示i会的技能,记录每个人状态压缩后的结果
for (int i = 0; i < n; i ++ ) {
int mask = 0;
for (int j = 0; j < people[i].size(); j ++ ) {
int bit = idx[people[i][j]];
mask |= (1 << bit);
}
skill[i] = mask;
}
long long dp[target + 1]; fill(dp, dp + target + 1, (1LL << n) - 1); // 0,1背包优化为1维,初始化为每个人都选,即最大值
dp[0] = 0; // 并集为0,所有人都不选
for (int i = 1; i <= n; i ++ ) {
for (int j = target; j >= 0; j -- ) {
auto t1 = dp[j]; // 不选 i
auto t2 = dp[j & ~skill[i - 1]] | (1L << (i - 1)); // 选 i
dp[j] = __builtin_popcountll(t1) < __builtin_popcountll(t2) ? t1 : t2; // 取其中人数最小的为答案
}
}
auto res = dp[target]; // 最终的二进制数,将其转化为所选的人的集合
int cnt = 0;
vector<int> ans;
while (res) {
if (res & 1) ans.push_back(cnt);
res >>= 1;
cnt ++ ;
}
return ans;
}
};
复杂度分析
时间复杂度:\(O(T+n2^m)\),其中 \(T\) 为 \(people\) 中的字符串的个数之和,\(n\) 为 \(people\) 的长度,\(m\) 为 \(reqSkills\) 的长度。
空间复杂度:\(O(n + m + 2^m)\)。

浙公网安备 33010602011771号