1. 题目来源
链接:40. 组合总和 II
必看,必刷,dfs 经典:
- [Mdfs] lc39. 组合总和(dfs+经典)
- [Mdfs] lc40. 组合总和 II(dfs+经典)
- [Mdfs] lc46. 全排列(dfs+经典)
- [Mdfs] lc47. 全排列 II(dfs搜索顺序+去重处理+知识理解+经典)
- [Mdfs] lc77. 组合(组合类型枚举+题目总结+经典)
- [Mdfs] lc78. 子集(二进制枚举+排列类型枚举+经典)
- [Mdfs] lc90. 子集 II(组合类型枚举+多重背包+去重经典)
2. 题目解析
前导题:
每个数可选 1 次和无限次的区别。当然组合中包含了重复元素,所以上题是个完全背包问题,而本题就是个多重背包问题。
本题就不仅需要受到 target
的限制,也要受到当前枚举数的个数的限制:
- 故先将数组排序,让重复元素排到一起方便拿双指针来计算其数量。
- 每次拿双指针计算当前枚举的元素的数量,再重复上题的枚举工作,加上数量限制即可。
- 但是
dfs
的时候,不再是u + 1
,因为u + 1
还可能是当前元素,但当前元素的所有数量都是在本层循环中来枚举的。所以下一次dfs
待枚举的数应该是k
,是下一段的第一个元素。 - 最后回溯恢复现场,加了几个就删除即可即可。
本题由于有数字的个数限制,本质上就是一次性将一个数的所有个数内的情况全部枚举完毕。求个数可以采用哈希表,也可以采用排序+双指针,枚举完这个数后,直接就是枚举下一个不同的数了,所以 dfs
在此的第二个参数值得注意!
同上题,不会分析,应该是指数级别的。
时间复杂度:不会分析…
空间复杂度:不会分析…
代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& c, int target) {
sort(c.begin(), c.end());
dfs(c, 0, target);
return ans;
}
void dfs(vector<int> &c, int u, int target) {
if (target == 0) {
ans.push_back(path);
return ;
}
if (u == c.size()) return ;
int k = u + 1;
while (k < c.size() && c[k] == c[u]) k ++;
int cnt = k - u;
for (int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
dfs(c, k, target - c[u] * i);
path.push_back(c[u]);
}
for (int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
path.pop_back();
}
}
};