【递归、搜索、回溯】专题四:综合练习

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

目录

一、字母大小写全排列

二、优美的排列

三*、N皇后

四、N皇后II

五*、有效的数独

六*、解数独

七、单词搜索

八、黄金矿工

九、不同路径III


一、字母大小写全排列

784. 字母大小写全排列 - 力扣(LeetCode)

问题就是,遍历到[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;
    }
};

二、优美的排列

526. 优美的排列 - 力扣(LeetCode)

画出决策树,思考剪枝条件:

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皇后

51. N 皇后 - 力扣(LeetCode)

画出决策树,那么每个结点就是在考虑当前行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

52. N 皇后 II - 力扣(LeetCode)

这次就是要问有多少种放法呗,思路还是一样的,再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;
            }
        }
    }
};

五*、有效的数独

36. 有效的数独 - 力扣(LeetCode)

思路如下:

仅需判断已经填入的数字是否符合即可。

创建二维数组,第一个位置表示行数或者列数,第二个位置表示具体的数字,由此可以表示某一行某一列是否有某数字。

判断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;
    }
};

六*、解数独

37. 解数独 - 力扣(LeetCode)

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

画出决策树,模拟填数,无非是遇到空的就填,填的时候需要特判,然后遍历下一层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;
    }
};

七、单词搜索

79. 单词搜索 - 力扣(LeetCode)

该题确实很像走迷宫的问题,对表格进行深度优先遍历,按照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;
    }
};

八、黄金矿工

1219. 黄金矿工 - 力扣(LeetCode)

找到不为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

980. 不同路径 III - 力扣(LeetCode)

依旧走迷宫,需要注意的就是怎么判断我从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;
            }
        }
    }
};

此篇完,美好的一天。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_dindong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值