第三十三天 | 51.N皇后 37.解数独

题目:51.N皇后

本题的难点在于本题是一个二维的数组,很陌生,应该怎么搜索呢?怎么搜棋盘呢?

在重申一遍回溯的优势,用递归取代多层for循环的嵌套

思路:

1.对于参数的设定:每个棋盘是一个二维数组,有N个棋盘即有N种解,所以定义result是一个三位数组

2.消除遍历二维矩阵的陌生感。

3.在实际问题中,用抽象符号来表示实际情况。本题用“Q”代表放了皇后,“,”代表没有放皇后。

代码能力还是很弱,代码细节上写不对,一些语法还是没有理解,第二轮的时候一定要注意。写出了以下错误代码。

class Solution {
public:
    vector<vector<string>> result;
    bool isValid(vector<string> chessboard, int col, int row, int n){         //判断该位置是否有效
        for(int i = 0; i < n; i++){     //判断本行是否有效
            if(chessboard[row][i] == "Q") return false;
        }   
        for(int i = 0; i < n; i++){
            if(chessboard[i][col] == "Q") return false;
        }
        for(int i = 0, j = 0; i < n && j < n; i++, j++){
            if(chessboard[i][j] == "Q") return false;
        }
        return true;
    }
    void backtracking(vector<string>& chessboard, int n, int row){
        if(row == n){
            result.push_back(chessboard);
        }

        for(int i = 0; i < n; i++){
            if(isValid(chessboard, i, row, n) == false) continue;     
            //如果判断该位置不能再放皇后,则跳过
            //传入i, row分别作为列数和行数
            chessboard[i][row] = "Q";
            backtracking(chessboard, n, row + 1);
            chessboard[i][row] = ",";
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<string> chessboard;              //如何进行初始化?
        backtracking(chessboard, n, 0);
        return result;
    }
};

难点在于判断isValid,代码如下

    bool isValid(vector<string>& chessboard, int col, int row, int n) {

    // 检查列

    for (int i = 0; i < row; i++) { // 这是一个剪枝

        if (chessboard[i][col] == 'Q') {

            return false;

        }

    }

    // 检查 45度角是否有皇后

    for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {

        if (chessboard[i][j] == 'Q') {

            return false;

        }

    }

    // 检查 135度角是否有皇后

    for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {

        if (chessboard[i][j] == 'Q') {

            return false;

        }

    }

    return true;

}

理解:

        1.backtracking函数中要向新的一行里填入元素,截至到目前,该行以上的每一行已经有位置填入了皇后,该行以下还没有(因为从递归深度上看还没到)。所以只用检查列上符不符合要求,也就是说控制列数不变,检查在其他行中该列位置有没有放过皇后。不需要对行进行检查,本行肯定还没有放过元素,因为一旦放了,必然会向下递归了,不会再在同一行停留。

        2.在检查列时进行的剪枝:根据1.所说,改行以下未填入元素,所以该行以下不用检查,只用检查以上的行中本列是否加入了皇后。故for循环的终止条件设置为i < row即可。

        3.检查45和135度角时用相同的原理进行剪枝。

树形结构如下(简化版):

完整代码如下:

class Solution {
public:
    vector<vector<string>> result;


    bool isValid(vector<string>& chessboard, int col, int row, int n) {
        // 检查列
        for(int i = 0; i < row; i++){            // 这是一个剪枝
            if(chessboard[i][col] == 'Q') return false;
        }
        // 检查 45度角是否有皇后
        for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
            if(chessboard[i][j] == 'Q') return false;
        }
        // 检查 135度角是否有皇后
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++){
            if(chessboard[i][j] == 'Q') return false;
        }
        return true;
    }


    void backtracking(vector<string>& chessboard, int n, int row){     //n表示for循环遍历的宽度,row表示向下递归的深度
        if(row == n){
            result.push_back(chessboard);
            return;
        }

        for(int col = 0; col < n; col++){
            if(isValid(chessboard, col, row, n) == false) continue;     
            //如果判断该位置不能再放皇后,则跳过
            //传入i, row分别作为列数和行数
            chessboard[row][col] = 'Q';
            backtracking(chessboard, n, row + 1);
            chessboard[row][col] = '.';
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        // vector<string> chessboard;              //如何进行初始化?
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(chessboard, n, 0);
        return result;
    }
};

有几个需要加深的问题:

        1.对result初始化时这是在干啥。

        2.string类型和char类型在赋值等操作时有什么不同和需要注意的地方。

题目:37.解数独(还需要多学几遍)

与n皇后的区别:n皇后明确说了每一行只能有一个皇后,所以你一行放成功了,就直接下一行,就直接进入下层的递归,而解数独,由于要每一行都要填,所以两次for去遍历,如果一个放成功了,就放右边的一个,也就是进入下层递归,直至最后一次递归发现填完了,也就是该返回最后的那一个true了,如果遇到了false,则不进入下次递归了,证明这次递归填的数有问题,还原后,继续填char的下一个,套娃到结果。本题比多皇后多了一个维度

尝试解答(及其不成熟):

终止条件:

        设置一个行数:row,if(row == 9),说明剩最后一行选择成功了,深度达到了9行,整个表就填完了。

本位置是否合法:

        每一行设置一个unordered_set<char> uset,来查询本层是否使用过了,那列上怎么判断呢?

正确思路:

        树形结构

        二维递归——两个for循环。

递归三步:

1.参数和返回值:返回值为bool类

        搜索整棵树用void,因为要收集到所有结果。只求一种解的情况用bool,得到一种true的解立即返回,意思就是说只用搜索到一枝正确的树枝。

2.终止条件:填满了最后一行,for循环会终止,所以即使不设置终止条件也会停止,跳出。

3.单层递归逻辑:

代码如下:

class Solution {
private:
    bool backtracking(vector<vector<char>>& board){
        for(int i = 0; i < board.size();  i++){              //遍历行
            for(int j = 0; j < board[0].size(); j++){        //遍历列
                if(board[i][j] == '.'){
                    for(int k = '1'; k <= '9'; k++){
                        if(isValid(i, j, k, board)){          //(i,j)这个位置放k是否合适
                            board[i][j] = k;                  //放置k
                            if(backtracking(board)) return true;      //如果这一步backtracking返回了true,说明该步填入的k是合适的,继续向上返回
                            board[i][j] = '.';                        //如果这一步backtracking返回了false,说明该步骤填了k,在后序的递归中会导致无解,所以本层应该尝试其他k值。这里进行回溯
                        }
                    }
                    return false;       //如果程序会执行到这一步,说明for循环找不到一个合适的k填进去,使数独有解,意味着在本层之前的数填的不合适。所以应该返回false告诉上面一层,上面一层需要换一个数。
                }
            }
        }
        return true;
    }
    bool isValid(int row, int col, char val, vector<vector<char>>& board) {
        for(int i = 0; i < 9; i++) {      //检查行里是否有元素
            if(board[row][i] == val) {
                return false;
            }
        }
        for (int j = 0; j < 9; j++){      //检查列里是否有元素  
            if(board[j][col] == val) {
                return false;
            }
        }
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for(int i = startRow; i < startRow + 3; i++){        //不理解这些数值
            for(int j = startCol; j < startCol + 3; j++){    //自己模拟一下
                if(board[i][j] == val){
                    return false;
                }
            }
        }
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

难点:

        1.最开始的true和false在哪里返回?

                返回false:如果for循环找不到一个合适的k填进去,使数独有解,说明这里填132456789都不合适,意味着在本层之前的数填的不合适。所以应该返回false告诉上面一层,上面一层需要换一个数。

                返回true:直到棋盘填满,if判断不会在为true,也就是说不会执行if里面的语句,直接返回true,然后一层一层向上返回true.

        2.判断是否isValid

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值