Java 求解-解数独

Posted 南淮北安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 求解-解数独相关的知识,希望对你有一定的参考价值。

一、题目

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

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

  • 数字 1-9 在每一行只能出现一次。
  • 数字 1-9 在每一列只能出现一次。
  • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

二、题解

N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来来遍历列,然后一行一列确定皇后的唯一位置。

本题就不一样了,「本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深」。

因为这个树形结构太大了,我抽取一部分,如图所示:


(1)确定递归函数以及参数

因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值

所以,递归函数的返回值需要是 bool 类型

boolean backTracking(char[][] board)

(2)确定递归终止条件

本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。

递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!

(3)确定单层搜索逻辑


在树形图中可以看出我们需要的是一个二维递归(也就是两个for循环嵌套着递归)

「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」

三、代码

class Solution {
    public void solveSudoku(char[][] board) {
        backTracking(board);
    }

    public boolean backTracking(char[][] board) {
        // 遍历行
        for (int i = 0; i < board.length; i++) {
            // 遍历列
            for (int j = 0; j < board.length; j++) {
                // 开始存放数字
                if (board[i][j] == '.') {
                    // 找到一个合适的数字
                    for (char ch = '1'; ch <= '9'; ch++) {
                        // 判断该数字是否合规
                        if (isValid(board, i, j, ch)) {
                            board[i][j] = ch;
                            // 找到一组合适的,立即返回
                            if (backTracking(board)) {
                                return true;
                            }
                            // 回溯
                            board[i][j] = '.';
                        }
                    }
                    // 9个数字都试完了,都不是合适,返回 FALSE
                    return false;
                }
            }
        }
        // 遍历完,没有返回 FALSE,说明找到了合适的排列
        return true;
    }
    // 判断当前位置是否可以存放 ch
    public boolean isValid(char[][] board, int row, int col, char ch) {
        // 判断当前行,当前列,是否已经存在 ch
        for (int i = 0; i < board.length; i++) {
            if (board[row][i] == ch || board[i][col] == ch) {
                return false;
            }
        }
        // 处理九宫格是否存在 ch
        row = (row / 3) * 3;
        col = (col / 3) * 3;
        for (int i = row; i < row + 3; i++) {
            for (int j = col; j < col + 3; j++) {
                if (board[i][j] == ch) {
                    return false;
                }
            }
        }
        return true;
    }
}

四、总结

该题,相对前面的回溯题目,是比较复杂的

大部分的回溯题目都是一维递归,该题涉及到了二维递归

需要考虑处理的情况比较复杂,因为每一个位置的情况都要考虑

可以注意下,对于九宫格判断是否合规的处理:

		// 处理九宫格是否存在 ch
        row = (row / 3) * 3;
        col = (col / 3) * 3;
        for (int i = row; i < row + 3; i++) {
            for (int j = col; j < col + 3; j++) {
                if (board[i][j] == ch) {
                    return false;
                }
            }
        }

这样处理,限制了九宫格每次的取值是0-3,4-6,7-9

以上是关于Java 求解-解数独的主要内容,如果未能解决你的问题,请参考以下文章

解题思路——解数独

HDU 1426 Sudoku Killer(dfs 解数独)

数独 算法 C语言 代码

python解数独

数独设计思路及全解

37解数独