专题五:DFS+回溯
LeetCode 17 电话号码的字母组合
1、分析
2、代码
LeetCode 79 单词搜索
1、分析
2、代码
LeetCode 46 全排列
1、分析
LeetCode 47 全排列II
1、分析
2、代码
LeetCode 78 子集
1、分析
2、代码
LeetCode 90 子集II
1、分析
2、代码
LeetCode 216 组合总和III
1、分析
2、代码
LeetCode 52 N皇后II
1、分析
2、代码
LeetCode 37 解数独
1、分析
2、代码
LeetCode 473 火柴拼正方形
1、分析
2、代码
首先需要强调下,DFS不等于递归,同样也可以用循环做,至于是否需要回溯,如果要求从当前分支进行搜索的状态需要一样,则需要回溯,即恢复现场。
题目:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/submissions/
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
在这里,我们用循环的方式来做这道题,例如:
class Solution {
public:
string num[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; //注意0和1
vector letterCombinations(string digits) {
if(digits.empty()) return vector();
vector res(1,"");
for(auto d:digits) //枚举数字
{
vector temp;
for(auto c:num[d-'0']) //枚举每个数字的备选字符
{
for(auto str:res) //枚举上一次结果
{
temp.push_back(str+c); //把新的数字加入到后面
}
}
res=temp; //更新结果
}
return res;
}
};
题目:https://leetcode-cn.com/problems/word-search/
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.
关于DFS,我们首先考虑的就是搜索的顺序,这个问题的顺序就是,从当前坐标位置出发,依次判断是否匹配,若匹配,则除了不能退回到原先位置外,可以往剩下的三个方向走。关于搜索截至条件,当当前字符不匹配时,则停止,返回false,还有就是匹配到了最后一个字符时,也停止,返回true。还有就是需要枚举起点。
所以我们再来讨论下,dfs()需要传入的参数,首先board和word肯定需要传进来,其次我们需要枚举起点坐标,所以还需要传入起点坐标x,y,再就是我们需要知道当前匹配到了第几个字符,再传入一个记录当前匹配到第几个字符u。所以dfs(board,x,y,word,u)
class Solution {
public:
int n,m;
int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
bool exist(vector>& board, string word) {
if(board.empty()||board[0].empty()) return false;
n=board.size(),m=board[0].size();
for(int i=0;i>& board,int x,int y,string &word,int u)
{
if(board[x][y]!=word[u]) return false;
if(u==word.size()-1) return true;
board[x][y]='-'; //标记已找到
for(int i=0;i<4;i++) //枚举4个方向
{
int nexx=x+dx[i];
int nexy=y+dy[i];
if(nexx>=0&&nexx=0&&nexy
题目:https://leetcode-cn.com/problems/permutations/
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
对于全排列问题,我们可以枚举每个位置放哪个数,也可以枚举每个数该放哪个位置。
在这里,我们用枚举每个位置该放哪个数
class Solution {
public:
vector> ans;
vector st; //标记当前位置是否可以放数
vector path;
int n;
vector> permute(vector& nums) {
if(nums.empty()) return ans;
n=nums.size();
st=vector(n,false);
dfs(nums,0);
return ans;
}
void dfs(vector& nums,int u)
{
if(u==n) //递归到了最后一层
{
ans.push_back(path);
return;
}
//枚举每个位置放哪个数
for(int i=0;i
题目:https://leetcode-cn.com/problems/permutations-ii/
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
这里采用每个数该放在哪个位置,用于重复的数放在两个不同的位置,算一个结果,所以我们可以人为规定下,按照原数组的顺序,比如数组中有两个1,即原来是哪个1在前,最后也还是在前,即第二个1我们搜索的顺序就从第一个1的下一个位置开始所搜,这样保证了顺序。所以我们的dfs()传参中,还得传一个从哪个位置开始搜索的变量。
class Solution {
public:
vector> ans;
vector path;
vector st;
int n;
vector> permuteUnique(vector& nums) {
n=nums.size();
path=vector(n);
st=vector(n,false);
sort(nums.begin(),nums.end()); //需要把相同数字放在一起
dfs(nums,0,0); //第0个数字从第0个位置开始枚举
return ans;
}
void dfs(vector& nums,int u,int start) //第u个数从第start位置开始枚举
{
if(u==n) //递归到了最后一个数
{
ans.push_back(path);
return;
}
for(int i=start;i
题目:https://leetcode-cn.com/problems/subsets/
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
对于判断数值i的第j位是否为1,可以用 i>>j&1 结果为1,则i的第j位为1。
class Solution {
public:
vector> ans;
vector> subsets(vector& nums) {
if(nums.empty()) return ans;
for(int i=0;i<1<1^nums.size()
{
vector path;
for(int j=0;j>j&1) //判断i的第j位是否为1
path.push_back(nums[j]);
}
ans.push_back(path);
}
return ans;
}
};
题目:https://leetcode-cn.com/problems/subsets-ii/submissions/
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
和上一个题不同的是,数组中包含重复数,所以我们这里来枚举每个数选择多少次,比如[1 ,2 ,2, 2, 3, 3],1:0,1;2:0,1,2,3;3:0,1,2。所以这里我们先递归枚举每个数,再依次枚举每个数选多少次。注意要计算相同数值的个数,需要排序下数组,让相同数字在一起。
class Solution {
public:
vector> ans;
vector path;
int n;
vector> subsetsWithDup(vector& nums) {
n=nums.size();
sort(nums.begin(),nums.end()); //有重复数字时,一定要排序
dfs(nums,0);
return ans;
}
void dfs(vector& nums,int u) //枚举每个数
{
if(u==n)
{
ans.push_back(path);
return;
}
//计算当前数的个数
int k=0;
for(;u+k
题目:https://leetcode-cn.com/problems/combination-sum-iii/submissions/
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
而对于dfs(),我们需要传入需要几个数k,当前数从哪一个位置u开始选择,需要达到的数据总和n。
class Solution {
public:
vector> ans;
vector path;
vector> combinationSum3(int k, int n) {
dfs(k,1,n);
return ans;
}
void dfs(int k,int u,int n) //一共需要k个数,从第u个数开始枚举,枚举数的总和为n
{
if(!k)
{
if(!n) ans.push_back(path);
return;
}
//枚举9个数
//从i枚举到9时,i~9之间个数必须大于k,9-i+1>=k --> i<=10-k
for(int i=u;i<=10-k;i++)
{
path.push_back(i);
dfs(k-1,i+1,n-i); //需要的数据减一,从下一个位置开始选择,需要达到的总和少i
path.pop_back();
}
}
};
题目:https://leetcode-cn.com/problems/n-queens-ii/
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
有n个皇后,保证每一行、每一列、正反对角线都不冲突,则每一行肯定有个皇后,所以我们可以递归每一行,在递归的过程中,枚举每一列,保证每一列没冲突,且对角线都不冲突。由于对角线上的皇后,横纵坐标的符号不同而已,我们可以用x+y表示正对角线,x-y表示反对角线,由于x-y可能为负,所以可以用x-y+n,保证数据不会越界。
class Solution {
public:
int ans;
int n;
vector col,diag,udiag;
int totalNQueens(int _n) {
n=_n;
col=vector(n,false); //列数组
diag=vector(2*n,false); //正对角线
udiag=vector(2*n,false); //反对角线
dfs(0); //递归每一行
return ans;
}
void dfs(int x)
{
if(x==n)
{
ans++;
return;
}
for(int i=0;i
题目:https://leetcode-cn.com/problems/sudoku-solver/
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
空白格用 '.' 表示。
一个数独。
答案被标成红色。
Note:
1-9
和字符 '.'
。9x9
形式的。我们从左上角开始枚举1~9个数,每次枚举时,都要保证行、列、九宫格都不冲突,即之前没有填过这个数。我们用三个bool型的数组来记录是否有哪个数了,例如row[1][2]=true,表示第1行有数值2了。
class Solution {
public:
//当前行、列、九宫格是否有哪个数
bool row[9][9]={0},col[9][9]={0},cell[3][3][9]={0};
int n,m;
void solveSudoku(vector>& board) {
if(board.empty()) return;
n=board.size(),m=board[0].size();
for(int i=0;i>& board,int x,int y)
{
if(y==m) x++,y=0;
if(x==n) return true;
if(board[x][y]!='.') return dfs(board,x,y+1);
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;
board[x][y]='1'+i;
if(dfs(board,x,y+1)) return true;
row[x][i]=col[y][i]=cell[x/3][y/3][i]=false;
board[x][y]='.';
}
}
return false;
}
};
题目:https://leetcode-cn.com/problems/matchsticks-to-square/submissions/
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
依次构造正方形的每条边。这是一个经典的剪枝问题。
剪枝:
class Solution {
public:
vector st;
bool makesquare(vector& nums) {
if(nums.empty()) return false;
int sum=0;
for(auto n:nums) sum+=n;
if(!sum||sum%4) return false;
//从大到小开始枚举
sort(nums.begin(),nums.end());
reverse(nums.begin(),nums.end());
st=vector(nums.size());
return dfs(nums,0,0,sum/4);
}
bool dfs(vector& nums,int u,int cur,int length)
{
if(cur==length) u++,cur=0; //找到一根
if(u==4) return true; //4根都找到
for(int i=0;i