上一篇我们学习了排列,子集,组合的一点经典例题,并且总结了解决这类问题需要用到的思路,现在是时候进行综合练习了!
目录
一、字母大小写全排列

问题就是,遍历到[pos]判断是改变是不改变,就和之前的题目选不选大概流程一致
你这么聪明,当然一眼就看出来怎么做咯(〃 ̄︶ ̄)人( ̄︶ ̄〃)
class Solution {
vector<string> ret;
string path;
public:
vector<string> letterCasePermutation(string s) {
dfs(s, 0);
return ret;
}
void dfs(string s, int pos) {
if (pos == s.length()) {
ret.push_back(path);
return;
}
char ch = s[pos];
// 不变
path.push_back(ch);
dfs(s, pos + 1);
path.pop_back();
// 改变
if (ch < '0' || ch > '9') {
char tmp = change(ch);
path.push_back(tmp);
dfs(s, pos + 1);
path.pop_back();
}
}
char change(char ch) {
if (ch >= 'a' && ch <= 'z')
ch -= 32;
else
ch += 32;
return ch;
}
};
二、优美的排列



画出决策树,思考剪枝条件:
1.类似全排列,需要check
2.按照题目要求特判
class Solution {
bool check[16];
int ret, edge;
public:
int countArrangement(int n) {
edge = n;
dfs(1);
return ret;
}
//pos:数组下标
void dfs(int pos) {
if (pos == edge + 1) //因为pos从1开始传
{
++ret;
return;
}
for (int i = 1; i <= edge; ++i) {
if (!check[i] && (i % pos == 0 || pos % i == 0)) {
check[i] = true;
dfs(pos + 1);
check[i] = false;
}
}
}
};
三*、N皇后


画出决策树,那么每个结点就是在考虑当前行Queen放在哪,下面思考如何剪枝

我们以每一行为每层递归处理的问题,每层递归仅仅考虑该层的Queen放在哪,这样该行就不用考虑了,因为Queen还可以攻击同列或者斜线上的元素,所以我们再创建三个数组当作check。col表示检查该列,dig1表示检查主对角线的元素,因为主对角线的元素是y=x+一个常数,所以说这个位置主对角线上的元素可以用这个常数表示,这个主对角线上的元素用dig1[y-x]表示吗?不对,因为y-x可能是负数的,那么此时我们再加上一个n就不会越界了,同时不违背一一对应,不违背唯一性。dig2表示检查副对角线元素副对角线就是y=-x+b,y+x是一个常量,用这个常量来标识这一个副对角线的元素。此时就不用+n了,因为y+x肯定大于0嘛。所以这也是为啥主副对角线的check数组(dig1和dig2)需要开成2*N的长度。
从这里我们也可以判断如何统一一条斜线上元素的选择二元性。
考虑完这些,这题也就搞定了。

class Solution {
vector<vector<string>> ret;
vector<string> path;
bool checkCol[10],checkDig1[20],checkDig2[20];
int edge;
public:
vector<vector<string>> solveNQueens(int n) {
edge=n;
path.resize(n);
for(int i=0;i<n;++i)
{
path[i].append(n,'.');
}
dfs(0);
return ret;
}
void dfs(int row)
{
if(row==edge)
{
ret.push_back(path);
return;
}
for(int col=0;col<edge;++col)
{
//因为主对角线是y-x 即 row-col 会出现负数,才要+n,副对角线是+所以不用+n
if(!checkCol[col]&&!checkDig1[row-col+edge]&&!checkDig2[row+col])
{
path[row][col]='Q';
checkCol[col]=checkDig1[row-col+edge]=checkDig2[row+col]=true;
dfs(row+1);
path[row][col]='.';
checkCol[col]=checkDig1[row-col+edge]=checkDig2[row+col]=false;
}
}
}
};
四、N皇后II


这次就是要问有多少种放法呗,思路还是一样的,再row==edage的时候++ret就行
class Solution {
bool checkCol[10],checkDig1[20],checkDig2[20];
int ret,edge;
public:
int totalNQueens(int n) {
edge=n;
dfs(0);
return ret;
}
void dfs(int row)
{
if(row==edge)
{
++ret;return;
}
for(int col=0;col<edge;++col)
{
if(!checkCol[col]&&!checkDig1[row-col+edge]&&!checkDig2[row+col])
{
checkCol[col]=checkDig1[row-col+edge]=checkDig2[row+col]=true;
dfs(row+1);
checkCol[col]=checkDig1[row-col+edge]=checkDig2[row+col]=false;
}
}
}
};
五*、有效的数独




思路如下:
仅需判断已经填入的数字是否符合即可。
创建二维数组,第一个位置表示行数或者列数,第二个位置表示具体的数字,由此可以表示某一行某一列是否有某数字。
判断3x3的格子里是否有重复的,因为我们需要定位到每一个3x3的格子,所以我们肯定需要一个二维数组,先定位到这个单元格,关于定位,可以用行数或者列数/3的方法,将每三格单元格化为为一个单位,然后我们需要对单元格内元素进行判重,那么就还需要一个位置,来存储我的num,所以这个判断3x3格子的数组设计为三维数组。
从而达到空间换时间的效果。
那么我们只需要一次二维遍历即可,遍历到,先判断这一行这一列这一单元格有没有相同的数字,有的话就直接返回flase了,然后再将其赋值为true即可。很简单哟,聪明的你一定可以想到,。
代码实现如下:
class Solution {
//因为我要填1~9所以第二个位置开10个大小
bool row[9][10],col[9][10],grid[3][3][10];
public:
bool isValidSudoku(vector<vector<char>>& board) {
for(int i=0;i<9;++i)
{
for(int j=0;j<9;++j)
{
if(board[i][j]!='.')
{
int num=board[i][j]-'0';
if(row[i][num]||col[j][num]||grid[i/3][j/3][num])return false;
row[i][num]=col[j][num]=grid[i/3][j/3][num]=true;
}
}
}
return true;
}
};
六*、解数独




本题需要我们自己将数字填完,形成有效数独,如果一个一个试,代码将会是庞大而繁杂的,这必然会导致超时。

画出决策树,模拟填数,无非是遇到空的就填,填的时候需要特判,然后遍历下一层dfs,遇到不符合的返回。如果下一层dfs也符合,那么也要返回,不要恢复现场了,因为每次dfs都在遍历整个表格,如果底层的dfs满足条件说明这个位置填数后的底层可以全部处理完毕,直接在上层终止回溯直接返回。
通过该题我们也了解到,当一个bool类型的函数不仅可以像void那样直接返回,比如return true等价于return;同时也可以作为递归退出的条件。
class Solution {
bool row[9][10],col[9][10],grid[3][3][10];
public:
void solveSudoku(vector<vector<char>>& board) {
//先将初始所有数全部填入bool数组
for(int i=0;i<9;++i)
{
for(int j=0;j<9;++j)
{
if(board[i][j]!='.')
{
int num=board[i][j]-'0';
row[i][num]=col[j][num]=grid[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)
{
if(board[i][j]=='.')
{
for(int num=1;num<=9;++num)
{
if(!row[i][num]&&!col[j][num]&&!grid[i/3][j/3][num])
{
board[i][j]=num+'0';
row[i][num]=col[j][num]=grid[i/3][j/3][num]=true;
//这里不能简单调用函数,而是需要将结果作为递归出口
//这也解释了为什么dfs要设计为bool类型
if(dfs(board))return true; //遇到递归结果正确,直接剪枝返回
//运行到这里说明这个位置填这个数不符合,恢复现场,遍历下一个
board[i][j]='.';
row[i][num]=col[j][num]=grid[i/3][j/3][num]=false;
}
}
//如果该空1-9都不符合,返回false,回到上一层恢复现场,也就是if(dfs)的位置
return false;
}
}
}
//全部都填完了,结束
return true;
}
};
七、单词搜索



该题确实很像走迷宫的问题,对表格进行深度优先遍历,按照word的顺序选择要走的点,依次往下dfs,不符合的回溯。
简单的一批不解释了
class Solution {
//迷宫问题,建立偏移常量
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0},m,n;
bool check[7][7];
public:
bool exist(vector<vector<char>>& board, string word) {
m=board.size(),n=board[0].size();
//找到第一个位置
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
if(board[i][j]==word[0])
{
check[i][j]=true;
//dfs设置为bool类型,即可当作void类型返回也可以当作条件
if(dfs(board,i,j,word,1))return true;
check[i][j]=false;
}
}
}
return false;
}
bool dfs(vector<vector<char>>& board,int i,int j,string word,int pos)
{
if(pos==word.size())return true;
for(int k=0;k<4;++k)
{
int x=i+dx[k],y=j+dy[k];
//注意越界判断,以及check检查
if(x>=0&&x<m&&y>=0&&y<n&&!check[x][y]&&board[x][y]==word[pos])
{
check[x][y]=true;
if(dfs(board,x,y,word,pos+1))return true;
check[x][y]=false;
}
}
//全部都不行返回false
return false;
}
};
八、黄金矿工


找到不为0的就进入深度遍历即可,
不能重复->check
走迷宫->dx,dx,dfs传参坐标xy
更新max没什么好说的
class Solution {
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0},ret,m,n;
bool check[16][16];
public:
int getMaximumGold(vector<vector<int>>& grid) {
m=grid.size(),n=grid[0].size();
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
if(grid[i][j]!=0)
{
check[i][j]=true;
dfs(grid,i,j,grid[i][j]);
check[i][j]=false;
}
}
}
return ret;
}
void dfs(vector<vector<int>>& grid,int i,int j,int sum)
{
for(int k=0;k<4;++k)
{
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]&&!check[x][y])
{
check[x][y]=true;
dfs(grid,x,y,sum+grid[x][y]);
check[x][y]=false;
}
}
ret=max(ret,sum);
}
};
九、不同路径III


依旧走迷宫,需要注意的就是怎么判断我从1到2是一个合法路径(全部走完了)只需要统计一下所有0的个数,然后dfs传参记录一下步数就行
class Solution {
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0},ret,target,m,n;
bool check[21][21];
int beginX,bgeinY;
public:
int uniquePathsIII(vector<vector<int>>& grid) {
m=grid.size(),n=grid[0].size();
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
//记录所有要走过的0
if(grid[i][j]==0)++target;
if(grid[i][j]==1){beginX=i;bgeinY=j;}
}
}
check[beginX][bgeinY]=true;
dfs(grid,beginX,bgeinY,0);
return ret;
}
void dfs(vector<vector<int>>& grid,int i,int j,int pos)
{
if(grid[i][j]==2)
{
if(pos==target+1)++ret;
return;
}
for(int k=0;k<4;++k)
{
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&!check[x][y]&&grid[x][y]!=-1)
{
check[x][y]=true;
dfs(grid,x,y,pos+1);
check[x][y]=false;
}
}
}
};
此篇完,美好的一天。

826

被折叠的 条评论
为什么被折叠?



