子集和问题
给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。
没有重复元素的情况
参考全排列解法
出现的问题
- 由于最后的结果与元素出现的顺序无关,所以需要去重
- 实际去重时,由于target很大,容易产生大量重复结果,去重开销很大
解决办法:剪枝
💡给定输入数组,搜索过程中选择的序列为,应当有,不满足该条件的选择序列容易造成重复,应当减去
为实现该剪枝,我们初始化变量 start
,用于指示遍历起始点。当做出选择 后,设定下一轮从索引 i 开始遍历。这样做就可以让选择序列满足 ,从而保证子集唯一
/* 回溯算法:子集和 I */
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
// 子集和等于 target 时,记录解
if (target == 0) {
res.push_back(state);
return;
}
// 遍历所有选择
// 剪枝二:从 start 开始遍历,避免生成重复子集
for (int i = start; i < choices.size(); i++) {
// 剪枝一:若子集和超过 target ,则直接结束循环
// 这是因为数组已排序,后边元素更大,子集和一定超过 target
if (target - choices[i] < 0) {
break;
}
// 尝试:做出选择,更新 target, start
state.push_back(choices[i]);
// 进行下一轮选择
backtrack(state, target - choices[i], choices, i, res);
// 回退:撤销选择,恢复到之前的状态
state.pop_back();
}
}
/* 求解子集和 I */
vector<vector<int>> subsetSumI(vector<int> &nums, int target) {
vector<int> state; // 状态(子集)
sort(nums.begin(), nums.end()); // 对 nums 进行排序
int start = 0; // 遍历起始点
vector<vector<int>> res; // 结果列表(子集列表)
backtrack(state, target, nums, start, res);
return res;
}
有重复元素时
那么本道题中每个元素只能使用一次,只需要在作出选择后,下一次选择从开始