频率次高的笔试题---回溯法类型/上
1、组合总和
1.1数字可以重复选取
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合(candidates 中的数字可以无限制重复被选取)
示例1
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
思路: 回溯法法,如果这条路走不下去,就退回一级,重新选择路径,一直往下走,直到 target 等于 0 就添加到返回列表中,如果 target 小于 0 则返回
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
if(candidates.size()==0){
return res;
}
combinationSum(candidates, target,0);
return res;
}
void combinationSum(vector<int>& candidates,int target,int index){
if(target < 0){
return;
}
if(target == 0){
res.push_back(temp);
}
for(int i = index;i<candidates.size();++i){
temp.push_back(candidates[i]);
combinationSum(candidates, target-candidates[i],i);
temp.pop_back();
}
}
private:
vector<vector<int>>res;
vector<int>temp;
};
1.2数字不可以重复选取
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
思路:同样也是回溯法,只不过每个元素只能使用一次,所以我们先得排序,按照升序或者降序的方式,在满足一个条件之后进行判断前一个元素和后一个元素是否相等,如果相等就跳过去重
注意:这里说的去重并不是每个值是唯一的,而是给定数组中的值只能使用一次
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
if(candidates.empty()){
return res;
}
sort(candidates.begin(),candidates.end());
combinationSum2(candidates,target,0);
return res;
}
void combinationSum2(vector<int>& candidates, int target,int index){
if(target<0){
return;
}
if(target == 0){
res.push_back(temp);
return;
}
for(int i = index;i<candidates.size();++i){
// 这个 if 语句还是挺难理解的,
// 目的防止在 candidates 中有重复的值被重复计算
// 而这个 if 语句在回溯的时候才会去判断,插入的时候不会判断
if(i>index && candidates[i] == candidates[i-1]){
continue;
}
// 下面这个 if 语句加不加无所谓,加上程序跑的快
// 如果当前的 candidates[i] 都已经大于 target 了
// 就说明这条路肯定不行,需要退回去
if(candidates[i]>target){
return;
}
temp.push_back(candidates[i]);
combinationSum2(candidates,target-candidates[i],i+1);
temp.pop_back();
}
}
private:
vector<vector<int>>res;
vector<int>temp;
};
1.3找出所有相加之和为 n 的 k 个数
每组数字只能含有 1- 9,不存在重复数字
class Solution {
public:
vector<vector<int>> combinationSum3(int k, int n) {
if(k > n){
return ans;
}
dfs(k,n,1);
return ans;
}
void dfs(int k,int n,int index){
if(k < 0 || n < 0){
return;
}
if(k == 0 && n == 0){
ans.push_back(tmp);
}
for(int i = index; i<10;++i){
tmp.push_back(i);
dfs(k-1, n-i ,i + 1);
tmp.pop_back();
}
return;
}
private:
vector<vector<int>>ans;
vector<int>tmp;
};
2、全排列
2.1 返回数字的序列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
解题思路:回溯法,在每一次的遍历都需要进行标记,如果访问了这个元素就标为 1,弹出这个元素就标 0 ,为的就是回溯的时候可以不重复
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<int>used(nums.size(),0);
dfs(used,nums);
return res;
}
void dfs(vector<int>&used,vector<int>& nums){
if(temp.size() == nums.size()){
res.push_back(temp);
return;
}
for(int i = 0;i<nums.size();++i){
if(used[i] !=0){
continue;
}
temp.push_back(nums[i]);
used[i] = 1;
dfs(used,nums);
temp.pop_back();
used[i] = 0;
}
}
private:
vector<vector<int>>res;
vector<int>temp;
};
方法二:交换的两个位置进行插入,然后再交换回去
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
if(nums.size() == 0){
return ans;
}
dfs(nums,0,nums.size()-1);
return ans;
}
void dfs(vector<int>&nums,int begin,int end){
if(begin == end){
ans.push_back(nums);
}
for(int i = begin;i<end + 1;++i){
swap(nums[i],nums[begin]);
dfs(nums,begin+1,end);
swap(nums[i],nums[begin]);
}
return;
}
private:
vector<vector<int>>ans;
};
2.2 字母大小写排列
给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。
输入 S = “a1b2”
输出: [“a1b2”, “a1B2”, “A1b2”, “A1B2”]
class Solution {
public:
vector<string> letterCasePermutation(string S) {
vector<string>ans;
backTrack(ans,S,0);
return ans;
}
private:
void backTrack(vector<string>&ans,string S,int index){
if(index == S.size()){
ans.push_back(S);
return;
}
// 不是字母的话
if(!isalpha(S[index])){
backTrack(ans,S,index+1);
}
else{
S[index] = tolower(S[index]);
backTrack(ans,S,index+1);
S[index] = toupper(S[index]);
backTrack(ans,S,index+1);
}
}
};
3、部分排列
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution {
public:
vector<vector<int>>ans;
vector<int>tmp;
vector<vector<int>> combine(int n, int k) {
dfs(n,k,0);
return ans;
}
void dfs(int n,int k,int index){
if(tmp.size() == k){
ans.push_back(tmp);
return;
}
for(int i = index+1;i < n+1;++i){
tmp.push_back(i);
dfs(n,k,i);
tmp.pop_back();
}
}
};
3、子集
3.1 不含重复元素数组
方法一:回溯法
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
ans.push_back(vector<int>());
vector<int>tmp;
back(nums,0,tmp);
return ans;
}
void back(vector<int>&nums,int start,vector<int>&tmp){
if(start >= nums.size()){
return;
}
for(int i = start;i < nums.size();++i){
tmp.push_back(nums[i]);
ans.push_back(tmp);
back(nums,i+1,tmp);
tmp.pop_back();
}
return;
}
private:
vector<vector<int>>ans;
};
方法二:利用位移思想
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>ans;
int n = nums.size();
int size = 1 <<n;
for(int i = 0;i<size;++i){
vector<int>tmp;
for(int j = 0;j<n;++j){
if(i & (1 <<j)){
tmp.push_back(nums[j]);
}
}
ans.push_back(tmp);
}
return ans;
}
3.2 含有重复元素数组
核心思想是进行去重
class Solution {
public:
vector<vector<int>>ans;
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int>tmp;
ans.push_back(vector<int>());
dfs(0,nums,tmp);
return ans;
}
void dfs(int start,vector<int>&nums,vector<int>&tmp){
if(start >= nums.size()){
return;
}
for(int i = start;i<nums.size();++i){
// 进行去重
if(i != start && nums[i] == nums[i-1]){
continue;
}
tmp.push_back(nums[i]);
ans.push_back(tmp);
dfs(i+1,nums,tmp);
tmp.pop_back();
}
}
};