赞
踩
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1: 输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 示例 2: 输入:n = 1, k = 1 输出:[[1]] # 提示 1 <= n <= 20 1 <= k <= n
组合问题不考虑一组数据的顺序,也就是说,一组数据是不是相等取决于该组数据元素是否完全相等: 例如 {1, 2} 与 { 2, 1 },是完全一样的组合。
明确了这一点, 题中给出的数据范围是 [1, n ],因此在做出选择之后应该禁止使用之前的元素,假设已经选取了 2, 那么[ 1, 2) 范围内的元素不可以再被选取,因为这些元素在之前的递归中肯定已经完成了选择(顺序选择进行递归)。
去重的过程在代码中体现在了每次递归选择的区间收缩,每次只选取区间[ startIndex, arr.size() ) 的元素。
codes:
class Solution { public: vector<int> vec;//暂存递归过程中的元素 vector<vector<int>> res;//存放最终结果 void backTrack(int& n, int& k, int start) { if (vec.size() == k) {//已经得到了一种组合: 本次递归结束 res.push_back(vec); return; } for (int i = start; i <= n; ++i) {//从第start开始进行选择,去除了之前的元素 vec.push_back(i); backTrack(n, k, i + 1); vec.pop_back();//回溯,撤销之前的选择,回到之前的状态 } } vector<vector<int>> combine(int n, int k) { backTrack(n, k, 1); return res; } };
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
示例 1: 输入: candidates = [2,3,6,7], target = 7 输出: [[7],[2,2,3]] 示例 2: 输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]] 示例 3: 输入: candidates = [2], target = 1 输出: [] 示例 4: 输入: candidates = [1], target = 1 输出: [[1]] 示例 5: 输入: candidates = [1], target = 2 输出: [[1,1]] 提示: 1 <= candidates.length <= 30 1 <= candidates[i] <= 200 candidate 中的每个元素都是独一无二的。 1 <= target <= 500
示例较长,简言之就是使用给定的数组进行组合,返回组合数相加等于target的解,其中原数组中的每个元素可以重复使用。
分析之后可以发现跟上一个组合问题非常相似:
稍有不同的体现在了本题的元素是可以重复使用的,因此在递归传参时需要注意。
代码中含有大量注释:
class Solution { public: vector<vector<int>> res; vector<int> vec; void backTrack(vector<int>& arr,int start,int sum ,const int& target,const int& n) { if (sum == target) {//满足条件,保存结果,退出递归 res.push_back(vec); return; } if (sum > target) return;//剪枝: 数组中都是正整数,此时已经大于目标值,不必要继续递归 for (int i = start; i < n; ++i) { sum += arr[i];//选择 arr[i],更新当前和 sum vec.push_back(arr[i]); backTrack(arr, i, sum, target, n);//这里传的是 i,保证可以重复选择该元素 vec.pop_back(); sum -= arr[i];//撤销之前的选择,回到"没选择之前"的状态 } } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { int n = candidates.size(); backTrack(candidates, 0, 0, target, n); return res; } };
需要注意的几个点:
if (sum > target)
return;//剪枝: 数组中都是正整数,此时已经大于目标值,不必要继续递归
通过此条件判断,省去了很多没有必要的递归,大大优化了时间复杂度。
递归传入的参数是当前选择的元素,因此在下一个递归函数中该元素依旧可以被使用,满足了题中:
candidates 中的数字可以无限制重复被选取。
for (int i = start; i < n; ++i) {
sum += arr[i];//选择 arr[i],更新当前和 sum
vec.push_back(arr[i]);
backTrack(arr, i, sum, target, n);//这里传的是 i,保证可以重复选择该元素
vec.pop_back();
sum -= arr[i];//撤销之前的选择,回到"没选择之前"的状态
}
引用Carl大哥精辟的总结:
对于组合问题,什么时候需要startIndex呢?
我举过例⼦,如果是⼀个集合来求组合的话,就需要startIndex,例如:回溯算法:求组合问题!,回 溯算法:求组合总和!。
如果是多个集合取组合,各个集合之间相互不影响,那么就不⽤startIndex,例如:回溯算法:电话号
码的字⺟组合
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
示例 1: 输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ] 示例 2: 输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ] 提示: 1 <= candidates.length <= 100 1 <= candidates[i] <= 50 1 <= target <= 30
又是一个组合问题,递归选择的范围需要不断收缩,与之前类似,通过一个变量start记录供选择的元素范围,还需要注意的是不可以有重复的组合,由于组合中包含重复元素,那么在选择的过程中可能会选择相同的元素进行递归,如图:
按照之前写组合问题的套路来操作这道题,会发现出现了相同的组合,显然这不满足题意。
如何去除这些重复的组合?
这里需要去除的重复是 同一层中的元素,简言之,树枝方向上的重复是针对某一个集合的,树层的重复是针对结果集的,因为我们需要在结果集中不包含重复解,所以需要在树层上进行操作。
实现代码:
class Solution { public: vector<vector<int>> res; vector<int> vec; vector<bool> flag; void backTrack(vector<int>& arr, int start, int& n, int sum, int& target) { if (sum == target) { res.push_back(vec); return; } if (sum > target) return; for (int i = start; i < n; ++i) { if (i > 0 && arr[i] == arr[i - 1] && flag[i - 1] == 0) { //剪掉了同一层上相同的元素 continue; } flag[i] = true; sum += arr[i]; vec.push_back(arr[i]); backTrack(arr, i + 1, n, sum, target); flag[i] = false; sum -= arr[i]; vec.pop_back(); } } vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { int n = candidates.size(); flag.resize(n, false); sort(candidates.begin(), candidates.end());//排序后方便判断 backTrack(candidates, 0, n, 0, target); return res; } };
使用标记数组 flag 记录每个树枝元素被选择的情况,当同一层出现相同元素时需要判断: arr[i] == arr[i - 1] && flag[i - 1] == false,为什么是这样判断:
上图展示了递归的调用过程,根据之前的结论: 重复出现在第⑨个调用过程,注意此时的标记数组flag已经被还原: [false, false,false, false], 选择完第二个元素2时变为
[false, true, false, false], arr[i] == arr[i - 1] && flag[i - 1] == false 的意思是当前元素与相邻的元素相等,并且之前的元素已经被选择过(因为是依次选择进行递归,现在被还原成了false未使用状态),注意:去重的操作是建立在元素有序的情况下。
参考: 「代码随想录」回溯算法精讲(v1.2)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。