回 溯 算 法 实 际 上 一 个 类 似 枚 举 的 搜 索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯 ” 返 回 , 尝 试 别 的 路 径 。 回 溯 法 是 一 种 选 优 搜 索 法 , 按 选 优 条 件 向 前 搜 索 , 以 达 到 目标。但 当 探 索 到 某 一 步 时 , 发 现 原 先 选 择 并 不 优 或 达 不 到 目 标 , 就 退 回 一 步 重 新 选 择 ,这 种 走 不 通 就 退 回 再 走 的 技 术 为 回 溯 法 , 而 满 足 回 溯 条 件 的 某 个 状 态 的 点 称 为 “ 回 溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯算法的模板:
1private void backtrack("原始参数") {
2 //终止条件(递归必须要有终止条件)
3 if ("终止条件") {
4 //一些逻辑操作(可有可无,视情况而定)
5 return;
6 }
7
8 for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++)
{
9 //一些逻辑操作(可有可无,视情况而定)
10
11 //做出选择
12
13 //递归
14 backtrack("新的参数");
15 //一些逻辑操作(可有可无,视情况而定)
16
17 //撤销选择
18 }
19 }
(1)leetcode 17. 电话号码的字母组合
class Solution {
string a[10] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
//这里的index指的是digits中的按键数字
void dfs(string digits,vector<string>&res, string temp, int index)
{
if(temp.length() == digits.size())
{
res.push_back(temp);
return;
}
//获取digits中的数
char c = digits[index];
string letters = a[c - '0'];
for(int i = 0; i <letters.length(); i++)
{
dfs(digits, res, temp + letters[i], index+1);
}
return;
}
public:
vector<string> letterCombinations(string digits) {
vector<string> res;
if(digits.length() == 0) return res;
string temp;
dfs(digits,res, temp, 0);
return res;
}
};
(2)leetcode39. 组合总和
注意:candicates中的元素可以重复使用,
错误示例
class Solution {
//start为candidates数组中元素的索引,为了避免重复
void dfs(vector<vector<int>>&res, vector<int> temp, vector<int> candidates,int remain)
{
if(remain == 0) //找到了恰好为target的数组
{
res.push_back(temp);
return;
}
else if( remain < 0) return;
else
{
for(int i = 0; i < candidates.size(); i++)
{
temp.push_back(candidates[i]);
dfs(res, temp, candidates, remain - candidates[i]);
temp.pop_back();
}
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> temp;
dfs(res, temp, candidates,target);
return res;
}
};
输入 [2,3,6,7] 7
预期结果为:[[2,2,3],[7]]
上述代码的结果为: [[2,2,3],[2,3,2],[3,2,2],[7]]
出现了重复,原因是在第一个数字选3之后,又返回去选了2.为了去重,在dfs函数的入参中,加入begin参数用来,使得当选择了3, 那么后一个数只能选择3 以及3以后的数。
class Solution {
//begin为candidates数组中元素的索引,为了避免重复
void dfs(vector<vector<int>>&res, vector<int> temp, vector<int> candidates,int begin,int remain)
{
if(remain == 0) //找到了恰好为target的数组
{
res.push_back(temp);
return;
}
else if( remain < 0) return;
else
{
for(int i = begin; i < candidates.size(); i++)
{
temp.push_back(candidates[i]);
dfs(res, temp, candidates, i,remain - candidates[i]);
temp.pop_back();
}
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> temp;
dfs(res, temp, candidates,0,target);
return res;
}
};
(3)leetcode40. 组合总和 II
先对candicates进行排序,然后在进行回溯
class Solution {
void dfs(vector<vector<int>>& res, vector<int> temp, vector<int> candidates,int begin, int remain)
{
if(remain == 0)
{
res.push_back(temp);
return;
}
else if(remain < 0) return;
else
{
for(int i = begin; i < candidates.size(); i++)
{
//注意是 i > begin
if(i > begin && candidates[i] == candidates[i-1]) continue;
else
{
temp.push_back(candidates[i]);
dfs(res, temp, candidates, i+1, remain - candidates[i]);
temp.pop_back();
}
}
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> temp;
sort(candidates.begin(), candidates.end());
dfs(res, temp, candidates, 0, target);
return res;
}
};
(4) leetcode 22. 括号生成
class Solution {
public:
//leftCount 表示左括号的数量 rightCount表示右括号的数量
void dfs(vector<string>& res, string temp, int leftCount, int rightCount, int n)
{
if(leftCount == n && rightCount == n)
{
res.push_back(temp);
return;
}
if(leftCount < rightCount) return;
if(leftCount < n) dfs(res, temp+'(', leftCount+1, rightCount, n);
if(rightCount < n) dfs(res, temp+')', leftCount, rightCount + 1, n);
}
vector<string> generateParenthesis(int n) {
vector<string> res;
string temp;
dfs(res, temp, 0, 0, n);
return res;
}
};
本题难点在于:结果中去重复,在本解法中,利用set (set底层实现采用的是二叉搜索树)
每次往res中添加结果时,需要判断set中是否已经包含了该元素。
class Solution {
unordered_set<string> set;
void dfs(string s, vector<string>&res, string temp, vector<bool>&isUsed)
{
if(temp.length()== s.length() && set.find(temp) == set.end())
{
set.insert(temp);
res.push_back(temp);
return;
}
for(int i = 0; i < s.length(); i++)
{
if(!isUsed[i])
{
isUsed[i] = true;
dfs(s, res, temp+s[i], isUsed);
isUsed[i] = false;
}
}
}
public:
vector<string> permutation(string s) {
vector<string> res;
string temp;
vector<bool> isUsed(s.length(), false);
dfs(s, res, temp, isUsed);
return res;
}
};