1.LeetCode17. 电话号码的字母组合
搜索顺序:
按照给出的数字依次进行搜索,对于每一个数字枚举它选用每一个字母的方案。
回溯:
当下一个字符串已经枚举完成时,需要更新当前字符串,此时需要进行回溯。然后进行枚举,到当前字符串的下一个元素。第一次写时,循环的意义没有考虑清楚,这个做法的循环是枚举当前字符串的每个字符,而不是下一个字符串的每个字符。
class Solution {
public:
string letter[10] = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> ans;
string path, s;
vector<string> letterCombinations(string _digits) {
if(_digits.size() == 0) return ans;
s = _digits;
dfs(0, 0);
return ans;
}
void dfs(int u, int y){
if(u >= s.size()){
ans.push_back(path);
return ;
}
string& cur = letter[s[u]-'2'];
//当前数字对应字符串的每一个字母都要遍历一遍
for(int i = 0; i < cur.size(); i ++){
path.push_back(cur[i]);
//遍历下一字符串
dfs(u+1, i);
path.pop_back();
}
}
};
2.LeetCode79. 单词搜索
搜索顺序:
对矩阵中的每一个元素依次进行搜索,对每一个元素枚举可能能走的四个方向。
回溯:
考虑清楚每个循环的意义。考虑清楚何时回溯。对于本题来说,当我已经尝试走过四个方向后,都没有匹配上,进行回溯。
class Solution {
public:
vector<vector<char>> b;
string w;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
bool dfs(int x, int y, int u){
//先判断字符不等的情况
if(b[x][y] != w[u]) return false;
//若有n-1个字母已经匹配,则成功
if(u >= w.size()-1) return true;
b[x][y] = '*';
for(int i = 0; i < 4; i ++){
int xt = x + dx[i], yt = y + dy[i];
if(xt >= 0 && xt < b.size() && yt >= 0 && yt < b[0].size() && b[xt][yt] != '*')
if(dfs(xt, yt, u + 1)) return true;
}
b[x][y] = w[u];
return false;
}
bool exist(vector<vector<char>>& board, string word) {
b = board, w = word;
for(int i = 0; i < b.size(); i ++){
for(int j = 0; j < b[0].size(); j ++){
if(dfs(i, j, 0)) return true;
}
}
return false;
}
};
3.LeetCode46. 全排列
搜索顺序:
对全排列中的位置依次进行搜索,对每一个位置枚举可能能填上的数。
回溯:
当第u+1个位置的所有方案填完后,回到第u个位置枚举第u位上的所有方案,此时回溯。
class Solution {
public:
vector<vector<int>> ans;
vector<int> nums, path;
vector<bool> st;
//dfs(u)表示当前填充到第u位了
void dfs(int u){
if(u == nums.size()){
ans.push_back(path);
return ;
}
//枚举每个位置上的备选数字
for(int i = 0; i < nums.size(); i ++){
if(!st[i]){
path.push_back(nums[i]);
st[i] = true;
dfs(u+1);
path.pop_back();
st[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& _nums) {
nums = _nums;
st.resize(nums.size());
dfs(0);
return ans;
}
};
4.LeetCode47. 全排列 II
搜索顺序:
将相同的元素都集中到一起,在填充位置时保证相同元素的相对位置不变。所以上一题dfs是对填充的位置进行搜索,对填充位置的数组元素进行枚举,本题dfs为保证相同元素的相对位置不变,对数组元素依次进行搜索,对存放数组的位置进行枚举。如果下一个递归的元素等于上一个递归的元素,则下一个递归的元素只能放在当前元素存放位置的后面。
回溯:
数组中第u+1个数枚举完所有位置时进行回溯。
class Solution {
public:
vector<vector<int>> ans;
vector<int> path, nums;
vector<bool> st;
unordered_map<int, int> pos;
//dfs表示用数组中第u个元素进行填充,填充从第t个位置开始
void dfs(int u, int t){
if(u == nums.size()){
ans.push_back(path);
return ;
}
//枚举能被填充的各个位置
for(int i = t; i < nums.size(); i ++){
//当位置i还没被使用过
if(!st[i]){
path[i] = nums[u];
st[i] = true;
//开始填充用第u+1位元素,如果下一位元素与当前元素相同需要放到第i位后面
//不同则从0号位置开始找空闲
dfs(u+1, u+1<nums.size() && nums[u+1] == nums[u] ? i+1 : 0);
st[i] = false;
//path下次递归可被自动覆盖
}
}
}
vector<vector<int>> permuteUnique(vector<int>& _nums) {
nums = _nums;
st.resize(nums.size()), path.resize(nums.size());
sort(nums.begin(), nums.end());
dfs(0, 0);
return ans;
}
};
5.LeetCode78. 子集
搜索顺序:
对数组中的元素依次进行搜索,对每一个元素可能加入集合,也可能不加。
回溯:
第u+1个元素的两种情况枚举完后,回溯。
class Solution {
public:
vector<vector<int>> ans;
vector<int> path, nums;
//对第u个位置上的元素进行选择
void dfs(int u){
if(u == nums.size()){
ans.push_back(path);
return ;
}
//不放入,直接进入第u+1个位置
dfs(u + 1);
//将第u个位置上的元素放入子集
path.push_back(nums[u]);
dfs(u + 1);
path.pop_back();
}
vector<vector<int>> subsets(vector<int>& _nums) {
nums = _nums;
dfs(0);
return ans;
}
};
用二进制数来表示第j位是否选择加入子集
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> path;
int n = pow(2, nums.size());
for(int i = 0; i < n; i ++){
path.clear();
for(int j = 0; j < nums.size(); j ++)
if(i >> j & 1) path.push_back(nums[j]);
ans.push_back(path);
}
return ans;
}
};
6.LeetCode90. 子集 II
搜索顺序:
在上一题的基础上推广,对数组中的元素依次进行搜索,同一个元素选择0~k次(k为相同元素的数量)。
回溯:
第u+1个元素选择0~k次的所有情况枚举完后,回溯。
class Solution {
public:
vector<int> nums, path;
vector<vector<int>> ans;
void dfs(int u){
if(u == nums.size()){
ans.push_back(path);
return ;
}
int k = 0;
while(u+k <nums.size() && nums[u+k] == nums[u]) k++;
//枚举第u个元素选择从0到k的不同情况
for(int i = 0; i <= k; i ++){
dfs(u+k);
path.push_back(nums[u]);
}
//恢复现场
for(int i = 0; i <= k; i ++) path.pop_back();
}
vector<vector<int>> subsetsWithDup(vector<int>& _nums) {
nums = _nums;
sort(nums.begin(), nums.end());
dfs(0);
return ans;
}
};
7.LeetCode216. 组合总和 III
搜索顺序:
按位置搜索,每一个位置都可填入在0~9之内,并比前一个数大的数。
回溯:
下一位置枚举完所有可能数后回溯。
class Solution {
public:
vector<int> res;
vector<vector<int>> ans;
int k, n;
//枚举到了第kt个数字,当前总和为nt,选择比t大的数
void dfs(int kt, int nt, int t){
if(k == kt){
if(nt == n) ans.push_back(res);
return ;
}else if(kt > k || nt > n) return ;
for(int i = t + 1; i <= 9; i ++){
res.push_back(i);
dfs(kt+1, nt+i, i);
res.pop_back();
}
}
vector<vector<int>> combinationSum3(int _k, int _n) {
k = _k, n = _n;
dfs(0, 0, 0);
return ans;
}
};
8.LeetCode52. N皇后 II
搜索顺序:
按行搜索,枚举每一列上可能的元素。
回溯:
下一行枚举完回溯。
class Solution {
public:
//列、对角线、反对角线是否放上皇后
vector<bool> col, dg, udg;
int ans, n;
//按行递归,在第u行上给皇后选位置
void dfs(int x){
if(x == n){
ans ++;
return ;
}
//枚举每列是否能放上皇后
for(int y = 0; y < n; y ++){
if(!col[y] && !dg[x-y+n] && !udg[x+y]){
col[y] = dg[x-y+n] = udg[x+y] = true;
dfs(x + 1);
col[y] = dg[x-y+n] = udg[x+y] = false;
}
}
}
int totalNQueens(int _n) {
n = _n;
col.resize(n), dg.resize(2*n), udg.resize(2*n);
dfs(0);
return ans;
}
};
9.LeetCode37. 解数独
搜索顺序:
从左到右,从上到下依次搜索。
回溯:
如果下一次搜索没有解,回溯。
class Solution {
public:
//每一行(列 or 单元格)的九个数字是否使用过
bool row[9][9], col[9][9], cell[3][3][9];
bool dfs(int x, int y, vector<vector<char>>& b){
if(y == 9) x ++, y = 0;
if(x == 9) return true;
if(b[x][y] == '.'){
for(int i = 0; i < 9; i ++){
if(!row[x][i] && !col[y][i] && !cell[x/3][y/3][i]){
row[x][i] = col[y][i] = cell[x/3][y/3][i] = true;
b[x][y] = '1' + i;
if(dfs(x, y + 1, b)) return true;
row[x][i] = col[y][i] = cell[x/3][y/3][i] = false;
b[x][y] = '.';
}
}
}
else return dfs(x, y + 1, b);
return false;
}
void solveSudoku(vector<vector<char>>& board) {
for(int i = 0; i < 9; i ++){
for(int j = 0; j < 9; j ++){
char c = board[i][j];
if(c != '.')
row[i][c-'1'] = col[j][c-'1'] = cell[i/3][j/3][c-'1'] = true;
}
}
dfs(0, 0, board);
}
};
10.LeetCode473. 火柴拼正方形
搜索顺序:
依次拼接正方形的每条边。每条边枚举木棒顺序从长到短(木棒越长,dfs的分支越少,分支下的方案数就越多,剪枝的效果就越好。
剪枝:
1、所有木棒长度的总和不是4的倍数或木棒不到4根。
2、如果当前木棒拼接失败,则跳过接下来所有长度相同的木棒。
3、如果当前木棒拼接失败,且是当前边的第一个,则直接剪掉当前分支。
4、如果当前木棒拼接失败,且是当前边的最后一个,则直接剪掉当前分支。
class Solution {
public:
int length;
vector<bool> st;
vector<int> nums;
bool makesquare(vector<int>& _nums) {
nums = _nums;
int sum = 0;
for (auto num : nums) sum += num;
if (nums.size() < 4 || sum % 4) return false;
length = sum / 4;
sort(nums.begin(), nums.end());
reverse(nums.begin(), nums.end());
st.resize(nums.size());
return dfs(0, 0, 0);
}
//拼接第u根木棒,当前长度为cur,从第start棵木棒开始选
bool dfs(int u, int cur, int start)
{
if (u == 4) return true;
if (cur == length) return dfs(u + 1, 0, 0);
for (int i = start; i < nums.size(); i ++ )
if (!st[i] && cur + nums[i] <= length)
{
st[i] = true;
if (dfs(u, cur + nums[i], i + 1)) return true;
st[i] = false;
while (i + 1 < nums.size() && nums[i + 1] == nums[i]) i ++ ;
if (!cur) return false;
if (cur + nums[i] == length) return false;
}
return false;
}
};