一、字母大小写全排列
1.链接
784. 字母大小写全排列 - 力扣(LeetCode)
2.描述
3.思路
首先,根据题意,这是一个不能重复的全排列,并且根据特定的规则,我们需要分三种情况,一种是当字符为数字时,则直接往下递归,为字母时,大小写分别递归,并且回溯
代码设计:
全局变量:path 、 ret
函数头:void dfs(string s,int pos)
剪枝:实际就是根据要求对字符进行分类递归即可
借用一下库里关于判断字符类型和大小写转化的函数,实际实现也不难
4.参考代码
class Solution {
string path;
vector<string> ret;
public:
vector<string> letterCasePermutation(string s)
{
dfs(s,0);
return ret;
}
void dfs(string s,int pos)
{
if(path.size() == s.size())
{
ret.push_back(path);
return;
}
if(isdigit(s[pos]))//数字
{
path+=s[pos];
dfs(s,pos+1);
path.pop_back();
}
if(isalpha(s[pos]))
{
char tmp = tolower(s[pos]); //小写
path+=tmp;
dfs(s,pos+1);
path.pop_back();
tmp = toupper(s[pos]);//大写
path+=tmp;
dfs(s,pos+1);
path.pop_back();
}
}
};
二、优美的排列
1.链接
526. 优美的排列 - 力扣(LeetCode)
2.描述
3.思路
一共有n个位置,每个位置要求不重复且无序的进行全排列,因此我们采用的bool数组的方式去进行剪枝,这里不要求返回数组,只要求记录符合要求的个数,因此,不需要path数组记录,而是直接带一个参数下标pos去表示,当前选择到第几个位置,只选择符合要求的路径,最后直到pos到达n+1时,意味着该路径是符合题意的,ret++;
4.参考代码
class Solution {
int count = 0;
bool cheak[16] = {false};
public:
int countArrangement(int n)
{
dfs(n,1);
return count;
}
void dfs(int n,int pos)
{
if(pos == n+1)
{
count++;
return;
}
for(int i = 1;i<=n;i++)
{
if(cheak[i] == false && (i%pos == 0 || pos%i == 0) )
{
cheak[i] = true;
dfs(n,pos+1);
cheak[i] = false;
}
}
}
};
三、N皇后
1.链接
51. N 皇后 - 力扣(LeetCode)
2.描述
3.思路
4.参考代码
class Solution {
vector<vector<string>> ret;
vector<string> path;
int aim = 0;
bool cheak_col[10] = {false};
bool cheak_dig1[20] = {false};
bool cheak_dig2[20] = {false};
public:
vector<vector<string>> solveNQueens(int n)
{
vector<string> tmp(n,string(n,'.'));
path = tmp;
aim = n;
dfs(0);
return ret;
}
void dfs(int row)
{
if(row == aim)
{
ret.push_back(path);
return;
}
for(int col =0;col<aim;col++)
{
if(!cheak_col[col] && !cheak_dig1[row-col+aim] && !cheak_dig2[row+col])
{
path[row][col] = 'Q';
cheak_col[col] = true;
cheak_dig1[row-col+aim] = true;
cheak_dig2[row+col] = true;
dfs(row+1);
path[row][col] = '.';
cheak_col[col] = false;
cheak_dig1[row-col+aim] = false;
cheak_dig2[row+col] = false;
}
}
}
};
四、有效的数独
1.链接
36. 有效的数独 - 力扣(LeetCode)
2.描述
3.思路
本题是为了给下一题的剪枝思考做铺垫,该题的思路就是如何判断一个数独合法
首先是当前列和行不能出现相同的数字,可以使用一个二维的bool数组,第一个[ ]内记录行(列)数,第二个[ ] 记录数字n
bool row[9][10]
bool col[9][10]
然后是每一个3*3的小区域,可以使用一个三维的数组,前两个格子锁定哪一块区域,最后一个格子记录数字
bool reg[3][3][10]
4.参考代码
class Solution
{
bool row[9][10] = {false};
bool col[9][10] = {false};
bool reg[3][3][10] = {false};
public:
bool isValidSudoku(vector<vector<char>>& board)
{
for(int i = 0;i<9;i++)
{
for(int j = 0;j<9;j++)
{
char ch = board[i][j];
if(ch!='.' && (row[i][ch-'0'] || col[j][ch-'0'] || reg[i/3][j/3][ch-'0']))
return false;
if(ch != '.')
{
row[i][ch-'0'] = true;
col[j][ch-'0'] = true;
reg[i/3][j/3][ch-'0'] = true;
}
}
}
return true;
}
};
五、解数独
1.链接
37. 解数独 - 力扣(LeetCode)
2.描述
3.思路
这题的剪枝策略和上题的思路是一样的,就是利用那三个bool数组去实现剪枝策略,首先要先记录下题目给的数据,将有数字的部分先记录true,然后就是关于dfs的设计了,我们可以将棋盘进行每个格子逐个去递归向下,每一个格子去尝试1到9的数字,当有一个格子1到9都不能合法时,意味着前面的某个数字虽然局部合法,但并非整体的解,因此需要告诉上一层数字选择不合理,就需要一个bool返回值,此外,当某次尝试成功时,不需要回溯,直接告诉上一层不需要继续尝试,直接返回结果即可,当所有值遍历完成后得到最终解返回true
这题的难点在于理解返回值的设置,这里会结合代码进行分析
4.参考代码
class Solution {
bool row[9][10] = {false};
bool col[9][10] = {false};
bool reg[3][3][10] = {false};
public:
void solveSudoku(vector<vector<char>>& board)
{
for(int i = 0;i<9;i++)//记录棋盘
{
for(int j = 0;j<9;j++)
{
char ch = board[i][j];
if(ch != '.')
{
int num = ch - '0';
row[i][num] = col[j][num] = reg[i/3][j/3][num] = true;
}
}
}
dfs(board);
}
bool dfs(vector<vector<char>>& board)
{
for(int i = 0;i<9;i++)
{
for(int j = 0;j<9;j++)
{
char ch = board[i][j];
if(ch == '.')
{
for(int num = 1;num<=9;num++)
{
if(!row[i][num] && !col[j][num] && !reg[i/3][j/3][num])
{
board[i][j] = '0'+num;
row[i][num] = col[j][num] = reg[i/3][j/3][num] = true;
if(dfs(board) == true) return true;
board[i][j] = '.';
row[i][num] = col[j][num] = reg[i/3][j/3][num] = false;
}
}
return false;
}
}
}
return true;
}
};
5.代码分析
总结
本章继续总结了经典dfs的题目,其中较为难的部分是根据题目的剪枝策略的思考,下一章会继续总结dfs关于二维中的深度搜索