回溯法和深度优先搜索
相同:回溯也是基于深度优先搜索的基础上实现的。
不同: 回溯是给定条件,在深度遍历的过程中,条件一旦不满足了就回头,下面的分支不再继续,因此通常和剪枝相结合,是顺序性推进的;深度遍历更像是全局搜索。
(1)1079活字印刷——回溯法解决子集全排列问题–中等
你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。
注意:本题中,每个活字字模只能使用一次。
其它知识点:
字符串处理
vector遍历
vector去重
class Solution {
public:
vector<string> serials;
//回溯+递归解决全排列问题
void dfs(vector<int> current_index, string track, string tiles){
//如果三个字符都添加过了就返回
if(current_index.size() == tiles.size()) return;
for(int i = 0; i< tiles.size(); i++){
//字符串拼接字符可以直接用+
//当前字符已经添加过了
if(find(current_index.begin(), current_index.end(), i) != current_index.end()) continue;
current_index.push_back(i);
track+=tiles[i];
serials.push_back(track);
dfs(current_index, track, tiles);
//恢复现场
track.pop_back();
current_index.pop_back();
}
return;
}
//删除vector中的重复元素
void erase(){
//1.使用sort对vector排序,sort的第三个参数可以使用一个简单的布尔类型函数进行选择,正序或逆序;
//2.使用unique将所有的重复元素放到末尾,返回的结果是一个迭代器类型的数据,就像vec.begin()那样。
//3.erase删除重复的内容,删除的区间是左闭右开
sort(serials.begin(),serials.end());
serials.erase(unique(serials.begin(),serials.end()),serials.end());
}
int numTilePossibilities(string tiles) {
if(tiles.size() == 1) return 1;
string track = "";
vector<int> current_index;
dfs(current_index, track, tiles);
// for(auto it = serials.begin(); it != serials.end();it++){
// cout << (*it) << "->";
// }
// cout<< serials.size();
// cout<< endl;
erase();
// for(auto it = serials.begin(); it != serials.end();it++){
// cout << (*it) << "->";
// }
// cout<< serials.size();
return serials.size();
}
};
(2)17. 电话号码的字母组合–中等
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
其它知识点:
map初始化
字符串拼接字符
class Solution {
public:
//map初始化
map<char, string> numToChar = {
{'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
};
vector<string> result;
void dfs(int i, string combination, string digits){
//将该按键对应的字母遍历添加
for(int j = 0; j < numToChar[digits[i]].size(); j++){
combination += numToChar[digits[i]][j];
if(i == digits.size()-1){
result.push_back(combination);
}
dfs(i+1, combination, digits);
//恢复现场
combination.pop_back();
}
}
vector<string> letterCombinations(string digits) {
string combination = "";
//从第一个数字开始,添加对应的字符
dfs(0, combination, digits);
return result;
}
};
(3)39–数组总和–中等
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
其它知识点:
排序
剪枝
class Solution {
public:
vector<vector<int>> results;
void dfs(vector<int> result_tmp, int sum, vector<int> candidates, int startid, int target){
// for(auto it = result_tmp.begin(); it != result_tmp.end(); it++){
// cout<< *it << "->";
// }
// cout<<"sum = " << sum << " startid = "<< startid <<endl;
//依次取各个数字
//cout<< startid << endl;
for(int i = startid; i< candidates.size(); i++){
//如果当前字符已经超了,就不需要继续添加后续更大的了,直接返回
sum = sum + candidates[i];
result_tmp.push_back(candidates[i]);
// for(auto it = result_tmp.begin(); it != result_tmp.end(); it++){
// cout<< *it << "->";
// }
// cout<< " sum="<<sum<<endl;
if(sum > target) {
//cout<<"overflowed"<<endl;
return;
}
else if(sum == target){
results.push_back(result_tmp);
return;
}
//此处从i+1开始,序号只增不减,防止重复数据
else{
dfs(result_tmp, sum, candidates, i+1, target);
}
//小于目标数值可以重复添加
int addTimes = 1;
while(sum < target){
//cout<< " "<< candidates[i] ;
sum = sum + candidates[i];
result_tmp.push_back(candidates[i]);
addTimes++;
// for(auto it = result_tmp.begin(); it != result_tmp.end(); it++){
// cout<< *it << "->";
// }
// cout<< " sum="<<sum<<endl;
//如果恰好相等
if(sum == target) {
//cout<< "add"<<endl;
results.push_back(result_tmp);
//退出当前层次循环,取下一个数字
break;
}
else if (sum < target){
dfs(result_tmp, sum, candidates, i+1, target);
}
else break;
}
//恢复现场
//cout<<"result_tmp.size = " << result_tmp.size()<<" start poping, addTime="<< addTimes<< endl;
while(addTimes > 0) {
result_tmp.pop_back();
sum = sum - candidates[i];
addTimes--;
//cout<< "result_tmp.size = " << result_tmp.size() << " addTime = " << addTimes << endl;
}
}cout<< endl;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
// cout<<"candidates: ";
// for(auto it = candidates.begin(); it != candidates.end(); it++){
// cout<< *it << "->";
// }
// cout<< endl;
dfs({}, 0, candidates, 0, target);
return results;
}
};
(4)78子集–中等
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
法一:递归
class Solution {
public:
//使用递归和回溯的思想实现
vector<vector<int>> result;
void generate(vector<int> nums, vector<int> tmp,int i){
if(i>= nums.size()) return;
//不添加当前数字
generate(nums, tmp, i+1);
tmp.push_back(nums[i]);
//添加当前数字,只有集合更新了,才需要添加到result集合里面
result.push_back(tmp);
generate(nums, tmp ,i+1);
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> tmp;
int i = 0;
result.push_back(tmp);
if(nums.size()==0) return result;
generate(nums,tmp,i);
return result;
}
};
法二:for循环
class Solution {
public:
//用for循环的方式实现
//每个数选与不选,共有2^n种可能
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
int all = 1<< nums.size();//用一位表示一个数字,1<<nums.size()即为2^n
for(int i=0; i<all; i++){
vector<int> item;
for(int j=0; j<nums.size(); j++){
//相当于100000...(共j个0,二进制)
//和2^n个i与各位分别相与,如果i!=0,则表示选中该位
if(i&(1<<j)){
item.push_back(nums[j]);
}
}result.push_back(item);
}
return result;
}
};
(5)90子集二–中等
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
与78子集的思想类似,多了判断是否重复的部分
class Solution {
public:
vector<vector<int>> result;
//此处集合要加上引用
void generate(vector<int> nums, vector<int> tmp, set<vector<int>>& res_set, int i){
if(i == nums.size()) return;
generate(nums, tmp, res_set, i+1);
tmp.push_back(nums[i]);
//如果该子集未重复
if(res_set.find(tmp) == res_set.end()){
res_set.insert(tmp);
result.push_back(tmp);
}
generate(nums, tmp, res_set, i+1);
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
//先对集合进行排序,升序
vector<int> tmp;
result.push_back(tmp);
if(nums.size() == 0) return result;
sort(nums.begin(), nums.end(), less<int>());
//确保集合中的各元素是升序的,以防集合1为[1,2,2],集合2为[2,1,2],但集合不判为相等的情况
int i = 0;
set<vector<int>> res_set;//用于去重,set去重所用时间复杂度为logN
res_set.insert(tmp);//集合的底层存贮结构为键值对
generate(nums, tmp, res_set, i);
return result;
}
};
(6)22括号生成–中等
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
class Solution {
public:
void generate(vector<string> &result, string tmp, int left, int right){
if(left == 0 && right == 0) {//左右括号都用完了就返回
result.push_back(tmp);
return;
}
//每次递归要么放左括号,要么放右括号
if(left > 0){
generate(result, tmp+"(", left-1, right);
}
if(right > left){//但是右括号是有条件的,有了左括号才能放右括号
generate(result, tmp+")", left, right-1);
}
}
vector<string> generateParenthesis(int n) {
int right = n;
int left = n;
vector<string> result;
string tmp;
generate(result, tmp, right, left);
return result;
}
};