解数独

#题目[1]

编写一个程序,通过已填充的空格来解决数独问题。一个数独的解法需要遵循如下规则:

  • 数字1-9在每一行只能出现一次;
  • 数字1-9在每一列只能出现一次;
  • 数字1-9在每一个以粗实线分隔的3×3宫内只能出现一次。

空白格用.表示。

sudoku-demo.png sudoku-demo-1.png

#思路

根据题干中给出的3条规则,容易想到采用状态数组记录数字1-9在每行、每列以及每个九宫格内是否出现以及出现的位置。

  • 采用boolean[][] row表示列行状态数组,用row[i][num]表示数字num出现在第i行。比如row[0][4] = true 表示数字5(实际数字范围是1-9)出现在第0行。

  • 采用boolean[][] col表示列状态数组,用col[j][num]表示数字num出现在第j行。比如col[0][5] = true表示数字6出现在第0列。

  • 采用boolean[][] block表示九宫格状态数组。粗实线将整个数独划分成了93×3 九宫格,因此按从左至右、从上至下的顺序将每个九宫格编号为0-8,用block[blockIndex][num]表示数字num 出现在第blockIndex九宫格。比如block[2][5]表示数字6出现在第2号九宫格。接下来的问题在于如何根据行下标i和列下标j 计算出九宫格的标号blockIndex。给出如下计算公式:

    blockIndex = i / 3 * 3 + j / 3

根据初始状态确定行、列和九宫格状态数组后,采用递归和回溯思想解数独。

  • 找到需要填数字的位置,即board[][] == '.'的位置;

  • 在每一个需要填数字的位置(i,j)填入数字num ,必须满足该数字没有在当前位置所在的行、列和九宫格中出现,即!row[i][num] && !col[j][num] && !block[blockIndex][num]为真;

  • 满足条件后将数字num填入,并更新该位置对应的状态数组中的值为true

  • 更新行坐标i和列坐标j,递归求解下一个需要填数字的位置;

    • 如果求解成功,继续递归;

    • 如果求解失败,需要回溯,将当前位置的对应的状态数组的值均设置为false,同时将数独中该位置的值重新设置为.

  • 递归结束条件:整个数独遍历完成。

#Java实现

求解数独类:

class Solution {
    /**
     * 解数独
     * @param board 数独
     */
    public void solveSudoku(char[][] board) {
        // 行状态数组
        boolean[][] row = new boolean[9][9];
        // 列状态数组
        boolean[][] col = new boolean[9][9];
        // 九宫格状态数组
        boolean[][] block = new boolean[9][9];

        // 记录数独的初始状态
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '1';
                    row[i][num] = true;
                    col[j][num] = true;
                    block[i / 3 * 3 + j / 3][num] = true;
                }
            }
        }
        // 解数独
        dfs(board, row, col, block, 0, 0);
    }

    /**
     * 递归+回溯
     *
     * @param board 数独
     * @param row   行
     * @param col   列
     * @param block 状态
     * @param i     行坐标
     * @param j     列座标
     * @return 结果
     */
    private boolean dfs(char[][] board, boolean[][] row, boolean[][] col, boolean[][] block, int i, int j) {
        // 遍历数独每个需要填数字的位置
        while (board[i][j] != '.') {
            // 对数独的每个位置进行遍历
            // 逐行遍历:若列坐标j大于等于9表示一行到头
            if (++j >= 9) {
                // 行坐标自增跳转下一行
                i++;
                // 列坐标归零从第一列开始
                j = 0;
            }
            // 当行坐标i大于等于9表示数独遍历完毕
            if (i >= 9) return true;
        }
        // 在需要填数字的位置填入数字
        for (int num = 0; num < 9; num++) {
            // 确定九宫格状态数组的位置坐标
            int blockIndex = i / 3 * 3 + j / 3;
            // 所填数字num未在该行、该列以及该九宫格出现时
            if (!row[i][num] && !col[j][num] && !block[blockIndex][num]) {
                // 将num填入board[i][j]
                // 由于num从0开始因此需要在数值上加1并转型位char类型
                board[i][j] = (char) ('1' + num);
                // 将row、col以及block状态数组对应得状态设置为true
                row[i][num] = true;
                col[j][num] = true;
                block[blockIndex][num] = true;
                // 递归解数独
                if (dfs(board, row, col, block, i, j)) return true;
                else {
                    // 若填入数字失败则回溯
                    row[i][num] = false;
                    col[j][num] = false;
                    block[blockIndex][num] = false;
                    board[i][j] = '.';
                }
            }
        }
        return false;
    }
}

求解测试类:

public class SudokuSolutionTest {
    public static void main(String[] args) {
        char[][] board = new char[][]{
                {'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'}
        };
        printBoard(board);
        Solution solution = new Solution();
        solution.solveSudoku(board);
        printBoard(board);
    }

    /**
     * 打印数独函数
     * @param board 数独
     */
    private static void printBoard(char[][] board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }
}
#测试结果

sudoku-solution.png


  1. 37. 解数独 ↩︎