经典回溯算法模板:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
【通过flag标志位判断数组中某一元素是否已被包含】
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size()==0) return res;
vector<int> arr;
vector<bool> flag(nums.size(),false); //标志位
dfs(nums,flag,res,arr);
return res;
}
void dfs(vector<int>& nums, vector<bool>& flag, vector<vector<int>>& res,vector<int>& arr){
if(arr.size()==nums.size())
res.push_back(arr);
for(int i=0;i<nums.size();i++){
if(flag[i]==false){ //更通用,尤其针对有重复元素,使用flag标志位
flag[i]=true;
arr.push_back(nums[i]);
dfs(nums,flag,res,arr);
flag[i]=false;
arr.pop_back();
}
}
}
};
或写成【通过find函数判断数组中是否已经包含某一元素】:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums){
vector<vector<int>> res;
if(nums.size()==0) return res;
vector<int> arr;
dfs(nums,arr,res);
return res;
}
void dfs(vector<int>& nums, vector<int>& arr, vector<vector<int>>& res){
if(arr.size()==nums.size()){
res.push_back(arr);
// return;
}
for(int i=0;i<nums.size();i++){
if(find(arr.begin(),arr.end(),nums[i])!=arr.end()) //针对无重复元素使用较好
continue;
arr.push_back(nums[i]);
dfs(nums,arr,res);
arr.pop_back();
}
}
};
【通过flag标志位判断数组中某一元素是否已被包含;并且flag也用于去重】
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size()==0) return res;
sort(nums.begin(),nums.end()); //先排序,再剪枝
vector<int> arr;
vector<bool> flag(nums.size(),false);
dfs(nums,flag,res,arr);
return res;
}
void dfs(vector<int>& nums, vector<bool>& flag, vector<vector<int>>& res,vector<int>& arr){
if(arr.size()==nums.size()) res.push_back(arr);
for(int i=0;i<nums.size();i++){
if(flag[i]==false){ //更通用,尤其针对有重复元素,使用flag标志位
if(i>0 && nums[i]==nums[i-1] && flag[i-1]==true) continue; //剪枝!!!去重
flag[i]=true;
arr.push_back(nums[i]);
dfs(nums,flag,res,arr);
flag[i]=false;
arr.pop_back();
}
}
}
};
或直接用库函数:
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size()==0) return res;
sort(nums.begin(),nums.end());
res.push_back(nums);
while(next_permutation(nums.begin(),nums.end()))
res.push_back(nums);
return res;
}
};
【使用start保证数组中无重复元素】
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> arr;
dfs(nums,0,arr,res);
return res;
}
void dfs(vector<int>& nums,int start,vector<int>& arr, vector<vector<int>>& res){
res.push_back(arr);
for(int i=start;i<nums.size();i++){ //注意,此处i=start! 若i从0开始,则不能满足无重复子集
arr.push_back(nums[i]);
dfs(nums,i+1,arr,res); //注意:此处i+1! 若为start+1,则会重复取值
arr.pop_back();
}
}
};
【使用start保证数组中无重复元素】
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end()); //先排序再剪枝
vector<vector<int>> res;
vector<int> arr;
dfs(nums,0,res,arr);
return res;
}
void dfs(vector<int>& nums,int start,vector<vector<int>>& res,vector<int>& arr){
res.push_back(arr);
for(int i=start;i<nums.size();i++){
if(i>start && nums[i]==nums[i-1]) continue; //剪枝!!去重
arr.push_back(nums[i]);
dfs(nums,i+1,res,arr);
arr.pop_back();
}
}
};
【使用start保证数组中无重复元素】
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> res;
if(n<0 || k<0) return res;
vector<int> arr;
dfs(n,k,1,res,arr);
return res;
}
void dfs(int n,int k,int start,vector<vector<int>>& res,vector<int>& arr){
if(arr.size()==k)
res.push_back(arr);
for(int i=start;i<=n;i++){
arr.push_back(i);
dfs(n,k,i+1,res,arr);
arr.pop_back();
}
}
};
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
#回溯+剪枝
res=[]
#if not candidates: return res
#candidates.sort()
arr=[]
self.dfs(candidates,target,arr,res,0)
return res
def dfs(self,candidates,target,arr,res,start):
if target==0:
#self.res.append(arr[:]) #注意:Python 中可变对象是引用传递,因此需要将当前 arr 里的值拷贝出来,或者使用 path.copy()
res.append(arr.copy())
return
for i in range(start,len(candidates)):
if target<0:
break
arr.append(candidates[i])
self.dfs(candidates,target-candidates[i],arr,res,i)
arr.pop()
总结:
子集问题:关键点在于要用一个 start 参数排除已选择的数字。
组合问题:关键点在于要用一个 start 排除已经选择过的数字。
排列问题:关键点在于使用 flag标志位 或者 find方法 排除已经选择的数字
多观察递归树的结构!!!