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)| 牛客网

原文地址:https://www.cnblogs.com/fortunely/p/14707121.html