回溯总结
1. 组合问题
-
组合问题 每次for都是从startIndex开始
-
每个元素 用一次和用多次体现在 backtrack(i+1)用一次还是backtrack(i)用多次上
-
组合问题不需要used数组,去重也不需要used数组那个判断
给定两个整数 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]]
思路
-
按回溯模板 直接写
-
注意剪枝操作, 超过k个元素不再进入递归 24ms->8ms
代码
class Solution {
public:
int n;
int k;
vector<int> path;
vector<vector<int>> all;
vector<vector<int>> combine(int n, int k) {
this->n = n;
this->k = k;
path.clear();
all.clear();
backtrack(1);
return all;
}
void backtrack(int num){
if(path.size() == k){
all.push_back(path);
return;
}
//进行剪枝, 超过k个元素不再进入递归 24ms->8ms
for(int i = num; i<=n - (k-path.size()) + 1; i++){
path.push_back(i);
backtrack(i+1);
path.pop_back();
}
}
};
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
思路
-
注意这个题 单个元素是可以重复使用的 表现在代码上 for循环内 backtrack是i 而不是i+1
-
sum 和 target 的判断逻辑注意下
代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(vector<int>& candidates, int index, int target, int sum){
//这里sum和target的判断逻辑注意下
if(sum>target) return;
if(sum == target) {
ans.push_back(path);
return;
}
for(int i = index; i<candidates.size(); i++){
path.push_back(candidates[i]);
backtracking(candidates, i, target, sum + candidates[i]); //注意 这里的i不加1 不然没有数重复的
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,0, target, 0);
return ans;
}
};
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
-
只使用数字1到9
-
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
思路
-
结束终止满足两个条件之一
nowSum >= n || path.size() == k
即可 -
满足条件的结果
nowSum == n && path.size() == k
代码
class Solution {
public:
vector<vector<int>> all;
vector<int> path;
int k,n ;
vector<vector<int>> combinationSum3(int k, int n) {
this->k = k;
this->n = n;
backtrack(1, 0);
return all;
}
void backtrack(int cur, int nowSum){
//if(cur>9) return; //注意这句不能要 不然会 9 45判错 最后cur 10 直接返回掉
if(nowSum >= n || path.size() == k){
if(nowSum == n && path.size() == k)
all.push_back(path);
return;
}
for(int i = cur; i<=9; i++){
path.push_back(i);
backtrack(i+1, nowSum + i);
path.pop_back();
}
}
};
给定一个候选人编号的集合 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]
]
思路
-
结束终止满足两个条件之一
nowSum >= n || path.size() == k
即可 -
满足条件的结果
nowSum == n && path.size() == k
代码
class Solution {
public:
//去重操作 数组中包含重复元素 所以要去重
//注意 并不需要used数组 used数组只在排列中用到?即(for(int i = 0)的时候用到)
vector<vector<int>> all;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
//排序让相同的元素挨着
sort(candidates.begin(), candidates.end());
backtrack(candidates, target, 0, 0);
return all;
}
void backtrack(vector<int>& candidates, int target, int curIndex, int nowSum){
if(nowSum>target) return;
if(nowSum == target){
all.push_back(path);
return;
}
for(int i = curIndex; i<candidates.size(); i++){
// 经典的去重操作 全排列中也用到了
if(i>curIndex && candidates[i] == candidates[i-1])
continue;
path.push_back(candidates[i]);
//这里是i+1,每个数字在每个组合中只能使用一次
backtrack(candidates, target, i+1, nowSum+candidates[i]);
path.pop_back();
}
}
};
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3
输出:0
思路
-
名为组合 实为排列 解法dp
-
回溯超时 dp解答
-
记忆化dfs 其实就是dp了吧
暴力回溯代码
class Solution {
public:
int ans;
int combinationSum4(vector<int>& nums, int target) {
ans = 0;
backtrack(nums, 0, target);
return ans;
}
void backtrack(vector<int>& nums, int nowSum, int target){
if(nowSum>target) return;
if(nowSum == target){
ans++;
return;
}
//可以反向 就是排列了
for(int i = 0; i<nums.size(); i++){
backtrack(nums, nowSum+nums[i], target);
}
}
};
记忆化dfs
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
return dfs(nums, target);
}
//备忘录,保存每层递归的计算结果,用于实现记忆化。
unordered_map<int, int> memo;
//dfs(target)的定义: 用nums中的元素凑成总和为target(每个元素可以使用多次),用多少中凑法。
int dfs(vector<int>& nums, int target){
if(target == 0)
return 1;
if(target < 0)
return 0;
if(memo.count(target) == 1)
return memo[target];
int res = 0;
for(int i = 0; i < nums.size(); i++){
res += dfs(nums, target - nums[i]);
}
memo[target] = res;
return res;
}
};
dp代码
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
//使用dp数组,dp[i]代表组合数为i时使用nums中的数能组成的组合数的个数
//dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+dp[i=nums[2]]+...
//举个例子比如nums=[1,3,4],target=7;
//dp[7]=dp[6]+dp[4]+dp[3]
//其实就是说7的组合数可以由三部分组成,1和dp[6],3和dp[4],4和dp[3];
vector<unsigned long long> dp(target+1);
//是为了算上自己的情况,比如dp[1]可以由dp【0】和1这个数的这种情况组成。
dp[0] = 1;
for(int i = 1; i<=target; i++){
for(int num : nums){
//dp用int的话 有一个很傻逼的越界,需要 && dp[i - num] < INT_MAX - dp[i]
if(i>=num)
dp[i] += dp[i-num];
}
}
return dp[target];
}
};