回溯刷题记录
导语
因学习全排列,却不懂原理和做法,因此将LeetCode中笔者所有尝试过的回溯题目归并到此随笔下,方便日后复盘学习。
例题
子集Ⅰ
题目
代码
class Solution {
public:
void dfs(vector<int>& nums, vector<int>& tmp, vector<vector<int>>& res, bool used[], int idx){
if (tmp.size() != 0){
res.emplace_back(tmp);
}
for(int i = idx; i < nums.size(); i++) {
if (!used[i]){
used[i] = true;
tmp.emplace_back(nums[i]);
dfs(nums,tmp,res,used,i + 1);
used[i] = false;
tmp.pop_back();
}
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
res.emplace_back(tmp);
bool used[10];
memset(used, 0, 10);
dfs(nums,tmp,res,used,0);
return res;
}
};
子集II
题目
代码
class Solution {
public:
void dfs(vector<int>& nums, vector<int>& tmp, vector<vector<int>>& res, bool used[], int idx){
if (tmp.size() != 0){
res.emplace_back(tmp);
}
for(int i = idx; i < nums.size(); i++) {
// 判断此路径是否已遍历
if (used[i] || (i > idx && nums[i] == nums[i - 1])){
continue;
}
used[i] = true;
tmp.emplace_back(nums[i]);
dfs(nums,tmp,res,used,i + 1);
used[i] = false;
tmp.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
res.emplace_back(tmp);
bool used[10];
memset(used, 0, 10);
sort(nums.begin(), nums.end());
dfs(nums,tmp,res,used,0);
return res;
}
};
全排列
题目
代码
class Solution {
public:
void dfs(vector<vector<int>>& res, vector<int>& nums, vector<int>& tmp, bool used[]){
if (tmp.size() == nums.size()) {
res.push_back(tmp);
return;
}
for (int i = 0; i < nums.size(); i++){
if(!used[i]){
used[i] = true;
tmp.push_back(nums[i]);
dfs(res, nums, tmp,used);
used[i] = false;
tmp.pop_back();
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
bool used[7];
memset(used,0,7);
dfs(res, nums, tmp, used);
return res;
}
};
全排列II
题目
代码
class Solution {
public:
void dfs(vector<int>& nums,vector<int> &tmp, vector<vector<int>>& res, bool used[], int idx){
if (tmp.size() == nums.size()) {
res.push_back(tmp);
return;
}
for(int i = 0; i < nums.size(); i++){
// 剪枝:判断是否为未使用的重复数字
if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) continue;
used[i] = true;
tmp.push_back(nums[i]);
dfs(nums,tmp,res,used,idx+1);
used[i] = false;
tmp.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
bool used[10];
sort(nums.begin(), nums.end());
dfs(nums,tmp,res,used, 0);
return res;
}
};
组合
题目
代码
class Solution {
public:
void dfs(vector<int>& nums, vector<int>& tmp, vector<vector<int>>& res, bool used[], int flag, int k){
if(tmp.size() == k) {
res.push_back(tmp);
return;
}
// 组合 不可以从后面开始取
// 全排列,可以先取后面的数字
for(int i = flag; i < nums.size(); i++) {
if (!used[i] ) {
used[i] = true;
tmp.push_back(nums[i]);
dfs(nums,tmp,res,used,i + 1, k);
used[i] = false;
tmp.pop_back();
}
}
}
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> res;
bool used[n];
memset(used, 0, n);
vector<int> nums;
for (int i = 1; i <= n; i++) nums.push_back(i);
vector<int> tmp;
dfs(nums,tmp,res,used, 0,k);
return res;
}
};
组合总和
题目
代码
class Solution {
public:
void dfs(int target, int sum, int idx, vector<int>& candidates,vector<int>& tmp, vector<vector<int>>& res) {
if(sum == target) {
res.push_back(tmp);
return;
}
// 按序记录
for (int i = idx; i < candidates.size(); i++) {
if (sum + candidates[i] <= target) {
tmp.push_back(candidates[i]);
dfs(target, sum + candidates[i], i, candidates,tmp, res);
tmp.pop_back();
}
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> tmp;
// 排序数组,使得数组有序。
sort(candidates.begin(),candidates.end());
dfs(target,0, 0, candidates,tmp, res);
return res;
}
};
组合总和II
题目
代码
class Solution {
public:
void dfs(int target, int sum, int idx, vector<int>& candidates,vector<int>& tmp, vector<vector<int>>& res) {
if(sum == target) {
res.push_back(tmp);
return;
}
// 按序记录
for (int i = idx; i < candidates.size(); i++) {
// 剪枝:如果第二次出现,直接跳过本循环。
if (i > idx && candidates[i] == candidates[i - 1]) continue;
if (sum + candidates[i] <= target) {
tmp.push_back(candidates[i]);
// 下一次迭代取i+1,此次的重复出现,不会被剪枝
dfs(target, sum + candidates[i], i + 1, candidates,tmp, res);
tmp.pop_back();
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> tmp;
// 排序数组,使得数组有序。
sort(candidates.begin(),candidates.end());
dfs(target,0, 0, candidates,tmp, res);
return res;
}
};
组合总和III
题目
[216. 组合总和 III - 力扣(LeetCode)](https://leetcode.cn/problems/combination-sum-ii/)
代码
class Solution {
public:
void dfs(vector<int>& nums, vector<int>& tmp, vector<vector<int>>& res, int n, int k, int idx, int sum){
if (sum == n && tmp.size() == k) {
res.push_back(tmp);
return;
}
for(int i = idx; i < 10; i++){
if (sum + nums[i] <= n) {
tmp.push_back(nums[i]);
dfs(nums,tmp,res,n,k,i + 1,sum + nums[i]);
tmp.pop_back();
}
}
}
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> res;
vector<int> tmp;
vector<int> nums;
for(int i = 1; i < 10; i++) nums.push_back(i);
dfs(nums,tmp,res,n,k,0,0);
return res;
}
};
组合总和IV
题目
超时代码
class Solution {
public:
void dfs(vector<int>& nums, vector<int>& tmp, vector<vector<int>>& res, int target, int sum){
if(sum == target) {
res.push_back(tmp);
return;
}
for(int i = 0; i < nums.size(); i++){
if (sum + nums[i] <= target) {
tmp.push_back(nums[i]);
dfs(nums,tmp,res, target,sum + nums[i]);
tmp.pop_back();
}
}
}
int combinationSum4(vector<int>& nums, int target) {
vector<vector<int>> res;
vector<int> tmp;
sort(nums.begin(), nums.end());
dfs(nums,tmp,res,target,0);
return res.size();
}
};
总结
回溯:是一种思想,通过遍历各个单位后,采用递归的方式,在原先的基础上继续寻找,直到找到所有符合要求结果。在回溯的过程中,存在不符合条件的路径,此时需要采用剪枝对回溯路径进行修改。同时回溯本质是暴力的枚举。其原理在于每一次回溯后,必须进行恢复现场,犹如回到起点。如对一个数字加上1和减1,最终得出结果不变。
剪枝:跳过不符合条件的回溯路径。
另外大多时候,问题如果有解便是最大的幸运,即使回溯本身性能太差。
未完待续。。。