zoukankan      html  css  js  c++  java
  • LeetCode> 37. 解数独 (回溯法)

    题目

    编写一个程序,通过填充空格来解决数独问题。

    数独的解法需 遵循如下规则:

    数字 1-9 在每一行只能出现一次。
    数字 1-9 在每一列只能出现一次。
    数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
    数独部分空格内已填入了数字,空白格用 '.' 表示。

    示例:

    输入:board = [["5","3",".",".","7",".",".",".","."],
    ["6",".",".","1","9","5",".",".","."],
    [".","9","8",".",".",".",".","6","."],
    ["8",".",".",".","6",".",".",".","3"],
    ["4",".",".","8",".","3",".",".","1"],
    ["7",".",".",".","2",".",".",".","6"],
    [".","6",".",".",".",".","2","8","."],
    [".",".",".","4","1","9",".",".","5"],
    [".",".",".",".","8",".",".","7","9"]]
    输出:[["5","3","4","6","7","8","9","1","2"],
    ["6","7","2","1","9","5","3","4","8"],
    ["1","9","8","3","4","2","5","6","7"],
    ["8","5","9","7","6","1","4","2","3"],
    ["4","2","6","8","5","3","7","9","1"],
    ["7","1","3","9","2","4","8","5","6"],
    ["9","6","1","5","3","7","2","8","4"],
    ["2","8","7","4","1","9","6","3","5"],
    ["3","4","5","2","8","6","1","7","9"]]
    

    解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

    提示:

    • board.length == 9
    • board[i].length == 9
    • board[i][j] 是一位数字或者 '.'
    • 题目数据 保证 输入数独仅有一个解

    解析

    思路:迭代回溯法

    用一个动态数组/列表p[]存放要填入数据的空格信息,p的第i个元素p[i][x,y,v]代表矩阵位置x行y列填入数据v。
    sum记录方案个数。
    

    下面来套用标准的回溯法。

    // 空格信息数组,三元组包含空格行号、列号、元素值信息
    vector<tuple<int,int,char>> p; 
    // 方案数
    int sum; 
    
    void init() {
        sum = 0;
        p初始化为包含空格信息的数组;
        ...
    }
    
    void backtrace(vector &board, int k) {
        if (k  >= p.size && sum == 0) { // 空格元素一共只有size个,迭代到size+1个意味着所有空格已经成功填完数字
            sum=1; // 已经找到一种方案
        }
        else {
            for 字符i = 字符1..9 // 要填写的数字字符 {
                // 检查通过,才填写元素
                if check(board, k, i) { 检查空格k是否能写字符i,如果能,则填写
                    place(board, k, i); // 空格k放置元素i
                    backtrace(board, k+1); // 迭代到空格k+1
    
                    if sum > 0  // 已经找到一种方案,不需要恢复board 以继续搜索其他方案
                        return;
                    unplace(board, k, i); // 恢复空格k放置的元素i
                }
            }
        }
    }
    
    // 检查空格k写元素c是否合理
    // 检查同行、同列、同宫格(3x3小矩阵)内是否有相同元素
    bool check(vector &board, int k, char c) {
    }
    
    // 空格k写元素c
    void place(vector &board, int k, char c)) {
    }
    // 撤销空格k写元素c,恢复之前的'.'
    void unplace(vector &board, int k, char c)) {
    }
    

    优化:每次要为空格k填入元素,而检查是否可行时,如果循环遍历每行、每列、每个宫格元素,会做很多无用查询。为了加快check检查速度,可以用3个9x9矩阵用于存放已经每行、每列、每个宫格字符1~9出现次数。(因为每个数字最多出现1次,也可以使用bool类型)。
    另外,放置数据和恢复数据时,统计次数的数组值也要及时更新。

    int rowcnt[9][9]; // 每行所包含的字符1~9的次数
    int colcnt[9][9]; // 每列所包含的字符1~9的次数
    int cubecnt[9][9]; // 每个宫格所包含的字符1~9的次数
    
    // 求宫格编号
    inline int getcubeid(int x, int y) {
        return (x / 3) * 3 + y / 3;
    }
    
    init, place, unplace略
    

    源代码

    class Solution {
    public:
        void print(vector<vector<char>> &board) {
            for (int i = 0; i < board.size(); ++i) {
                for (int j = 0; j < board[i].size(); ++j) {
                    cout << board[i][j];
                    if (j < board[i].size() - 1) cout << " ";
                    else cout << endl;
                }
            }
        }
    
        bool check(vector<vector<char>>& board, int k, char c) {
            if (c < '1' || c > '9') {
                std::cout << "illegal place value" << endl;
                return false;
            }
    
            int x = get<0>(p[k]);
            int y = get<1>(p[k]);
            int v = c - '1';
    
            // 检查同一列
            if (colcnt[y][v] > 0) return false;
    
            // 检查同一行
            if (rowcnt[x][v] > 0) return false;
    
            // 检查3x3宫格
            if (cubecnt[(x / 3) * 3 + y / 3][v] > 0) return false;
    
            return true;
        }
    
        /**
        * 空格k 放置字符c
        */
        void place(vector<vector<char>>& board, int k, char c) {
            int x = get<0>(p[k]);
            int y = get<1>(p[k]);
    
            // 填入数字i
            get<2>(p[k]) = c;
            board[x][y] = c;
    
            int v = c - '1';
            rowcnt[x][v]++;
            colcnt[y][v]++;
            cubecnt[(x/3)*3 + y/3][v]++;
        }
    
        /**
        * 恢复空格k 放置字符c前状态
        */
        void unplace(vector<vector<char>>& board, int k, char c) {
            int x = get<0>(p[k]);
            int y = get<1>(p[k]);
    
            // 恢复填入的数字前状态
            get<2>(p[k]) = '.';
            board[x][y] = '.';
    
            int v = c - '1';
            rowcnt[x][v]--;
            colcnt[y][v]--;
            cubecnt[(x/3)*3 + y/3][v]--;
        }
    
        /**
        * 迭代回溯法求出1个解
        * @param k 空格k,通过空格数组p[k]访问
        */
        void backtrace(vector<vector<char>>& board, int k) {
            if (k >= p.size() && sum == 0) {
                sum++;
                // print(board);
            }
            else {
                // 空格k依次填入1..9
                for (char i = '1'; i <= '9'; ++i) {
                    if (check(board, k, i)) {
                        place(board, k, i);
                        backtrace(board, k + 1);
    
                        if (sum > 0) return ;
                        unplace(board, k, i);
                    }
                }
            }
        }
    
        void init(vector<vector<char>>& board) {
            sum = 0;
    
            for (int i = 0; i < 9; ++i) {
                for (int j = 0; j < 9; ++j) {
                    rowcnt[i][j] = 0;
                    colcnt[i][j] = 0;
                    cubecnt[i][j] = 0;
                }
            }
    
            // 记录空格位置信息, 统计元素出现次数信息
            for (int i = 0; i < board.size(); ++i) {
                for (int j = 0; j < board[i].size(); ++j) {
                    
                    if (board[i][j] == '.')
                        p.push_back(make_tuple(i, j, board[i][j]));
                    else {
                        int value = board[i][j] - '1';
                        if (value < 0 || value >= 9) { // 确保数字一定是1~9, 对应value范围0~8
                            std::cerr << "input vector include illegal value at (" << i << ", " << j << ")" << endl;
                            exit(1);
                        }
    
                        rowcnt[i][value]++;
                        colcnt[j][value]++;
                        cubecnt[(i / 3) * 3 + j / 3][value]++;
                    }
                    
                }
            }
        }
        
        // 提供的入口
        void solveSudoku(vector<vector<char>>& board) {
            if (board.empty()) return;
    
            init(board);
    
            if (p.empty()) {
                cout << "the martix has been a sudoku" << endl;
                return ;
            }
    
            backtrace(board, 0); // 从空格0开始回溯
        }
    
    private:
        vector<tuple<int, int, char>> p; // 空格数组,包含了行号、列号、元素值
        int sum; // 方案数
        int rowcnt[9][9]; // (0~8)每行出现数字1~9计数
        int colcnt[9][9]; // (0~8)每列出现数字1~9计数
        int cubecnt[9][9]; // 每个宫格出现数字1~9计数
    };
    

    来源:37. 解数独 | leetcode
    类似题目:
    HJ44 数独(Sudoku)| 牛客网

  • 相关阅读:
    【CYH-02】noip2018数论模拟赛:赛后题解
    C语言 malloc函数
    C 库函数
    C语言strcmp()函数:比较两个字符串
    C语言sprintf函数的深入理解
    C语言strcat()函数:字符串连接(拼接)
    liunx 中设置zookeeper 自启动(service zookeeper does not support chkconfig)
    页面上出现403错误,并解决
    Mac 下安装nginx
    nginx: [emerg] unknown directive "," in /usr/local/etc/nginx/nginx.conf:80
  • 原文地址:https://www.cnblogs.com/fortunely/p/14707121.html
Copyright © 2011-2022 走看看