Bootstrap

代码随想录算法训练营 |回溯算法:part04|重新安排形成、N皇后、解数独

332. 重新安排行程 - 力扣(LeetCode)//困难。脑子不行。。跳过了。二刷再说。。。

51. N 皇后 - 力扣(LeetCode)//困难。理解思路就好写了。

class Solution {
private:
vector<vector<string>> result;
vector<string> path;
    bool Isvaild(int n,int row,int col,const vector<string>& path){//核验某特定坐标是否满足要求。
        for(int i=0;i<row;i++){//验证某列
            if(path[i][col]=='Q')
            return false;
        }
        for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){//验证对角线
            if(path[i][j]=='Q')
            return false;
        }
        for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++){
            if(path[i][j]=='Q')
            return false;            
        }
        return true;           
    }
    void addpath(int n,int row){
        if(row==n){
            result.push_back(path);
            return;
        }
        string s(n,'.');
        path.push_back(s);
        for(int i=0;i<n;i++){ 
            if(Isvaild(n,row,i,path)){
                path[row][i]='Q';
                addpath(n,row+1);
                path[row][i]='.';
            }
        }
        path.pop_back();
    }
public:
    vector<vector<string>> solveNQueens(int n) {  
        result.clear();
        path.clear();
        if(n==1){
            path.push_back("Q");
            result.push_back(path);
        }
        else if(n>3){
            addpath(n,0);
        }
        return result;
    }
};

37. 解数独 - 力扣(LeetCode)//困难。看过思路也容易下手。

class Solution {
private:
    int chartoint(const vector<vector<char>>& board,int row,int col){
        int i=board[row][col]-'0';
        return i;
    }
    void boardinsert(vector<vector<char>>& board,int row,int col,int nums){
        char i=nums+'0';
        board[row][col]=i;
    }
    bool Isvaild(const vector<vector<char>>& board,int row,int col,int temp){
        char nums=temp+'0';
        for(int i=0;i<9;i++){//检查同列
            if(i!=row&&board[i][col]==nums) return false;
        }
        for(int i=0;i<9;i++){//检查同行
            if(i!=col&&board[row][i]==nums) return false;
        }        
        int temprow=3*(row/3);int tempcol=3*(col/3);
        for(int i=temprow;i<temprow+3;i++){//检查3X3格
            for(int j=tempcol;j<tempcol+3;j++){
                if(i!=row&&j!=col&&board[i][j]==nums) return false;
            }
        }
        return true;
    }
    bool findanswer(vector<vector<char>>& board){
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]!='.') continue;
                for(int nums=1;nums<10;nums++){
                    if(Isvaild(board,i,j,nums)){
                        boardinsert(board,i,j,nums);
                        if(findanswer(board)) return true;
                        board[i][j]='.';
                    }
                }
                return false;//9个没找到即是无解。
            }
        }
        return true;//递归的最深处,此时棋盘满了,因此需要返回true确认已经找到结果
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        findanswer(board);
    }
};

当结果唯一时最好使用带返回值的函数,方便找到一个解时立刻结束函数并返回

记录一个基于该算法下的优化方案(来自AI的讲解):

一、通过构建三个9位的bitset数组分别记录行列子格记录了当前行,当前列,当前子格已经占据的数字。使得判断速度提升。

二、先从两个for循环全图遍历,找到一个当前候选数最少的一个位置,从这个点开始递归回溯。因此树状图的第一层就比原先的算法少了。

这个算法比原来的快了2倍以上。。。

class Solution {
private:
    vector<bitset<9>> row_flags;   // 记录每行数字1-9的存在情况(位压缩)
    vector<bitset<9>> col_flags;   // 记录每列数字1-9的存在情况
    vector<bitset<9>> block_flags; // 记录每个3x3子格数字1-9的存在情况

    // 判断数字num是否可以填入(row, col)位置
    bool isValid(int row, int col, int num) {
        int d = num - 1; // 将数字1-9转换为bitset的索引0-8
        int k = (row / 3) * 3 + (col / 3); // 计算子格索引(0-8)
        // 检查行、列、子格中是否均未出现过该数字
        return !row_flags[row][d] && !col_flags[col][d] && !block_flags[k][d];
    }
    // 回溯求解核心逻辑
    bool backtrack(vector<vector<char>>& board) {
        int min_count = 10;       // 记录当前最小候选数数量
        int best_row = -1, best_col = -1; // 候选数最少的位置坐标
        vector<int> best_candidates;      // 该位置的候选数字列表

        // 遍历整个数独板,寻找候选数最少的空位(剪枝关键)
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') continue; // 跳过已填数字

                vector<int> candidates;
                // 收集当前空位的所有合法候选数字
                for (int num = 1; num <= 9; ++num) {
                    if (isValid(i, j, num)) {
                        candidates.push_back(num);
                    }
                }

                if (candidates.empty()) return false; // 存在无解的空位,直接回溯

                // 更新最优候选位置(优先处理可能性最少的位置)
                if (candidates.size() < min_count) {
                    min_count = candidates.size();
                    best_row = i;
                    best_col = j;
                    best_candidates = candidates;
                    if (min_count == 1) break; // 已找到唯一候选,无需继续搜索
                }
            }
        }

        if (best_row == -1) return true; // 所有位置已填满,找到解

        // 尝试所有候选数字
        for (int num : best_candidates) {
            int d = num - 1; // 转换为bitset索引
            int k = (best_row / 3) * 3 + (best_col / 3); // 子格索引

            // 更新状态标记
            row_flags[best_row].set(d);
            col_flags[best_col].set(d);
            block_flags[k].set(d);
            board[best_row][best_col] = num + '0'; // 填入数字

            // 递归求解
            if (backtrack(board)) return true;

            // 回溯恢复状态
            board[best_row][best_col] = '.';
            row_flags[best_row].reset(d);
            col_flags[best_col].reset(d);
            block_flags[k].reset(d);
        }

        return false; // 当前路径无解
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        // 初始化bitset数组
        row_flags.resize(9);
        col_flags.resize(9);
        block_flags.resize(9);

        // 预处理已有数字,填充标志位
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') {
                    int d = board[i][j] - '1'; // 字符转数字索引('1'->0 ... '9'->8)
                    int k = (i / 3) * 3 + (j / 3); // 计算子格索引
                    row_flags[i].set(d);    // 标记行
                    col_flags[j].set(d);    // 标记列
                    block_flags[k].set(d);  // 标记子格
                }
            }
        }

        backtrack(board); // 启动回溯算法
    }
};

;