元宵节无法出门闹花灯,那么在家用程序解数独吧

今天是元宵佳节,又恰逢周末,原本应该出门闹花灯,但是当前的新冠疫情让大家关门闭户异常冷清。
猜灯谜是元宵节的传统项目,记得小时候在老家每个元宵节都打着灯笼出门,大街上满是彩灯和灯谜,城市还专门组织有大型的烟火表演,人山人海热闹非凡。
既然没法出门,在家也能做些个游戏,例如用看怎么用代码来解决数独问题吧:)
我们有如下数独问题:


image.png

这是一个9X9的数独矩阵,其中需要保证:

  1. 整数1~9在每一行中只能出现一次
  2. 整数1~9在每一列中只能出现一次
  3. 在其中9个3X3的子矩阵中,整数1~9也只能出现一次

通过以上限制,我们期望得到以下的答案:


image.png

为了能用程序来解决数独问题,我们用一个二维字符数组来代表字符矩阵:

        char[][] 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'}
        };

其中未知的单元格用字符'.'来表示,期望实现个函数将所有'.'替换为'1'~'9'的字符并保证数独矩阵的合法性。
我们可以将此问题分解为两个子问题来考虑:检验放入一个值后,是否满足数独矩阵合法性;递归遍历整个矩阵,并在未知位置依次试探放入'1'~'9'并检查合法性,如果不合法则使用下一个值进行回溯分析。

校验当前单元格放入数值后的合法性

要校验合法性,满足数据矩阵所要求的三个规则即可:

    /**
     * 校验放入当试探值是否能保证数独矩阵的正确性
     *
     * @param board 数独矩阵
     * @param x x轴坐标代表列号
     * @param y y轴坐标代表行号
     * @param c 试探放入的值,为'1'~'9'的整数
     * @return 返回true当校验通过
     */
    private boolean isValid(char[][] board, int x, int y, char c) {
        //3*3方块的开始y轴号
        int regionRow = 3 * (y / 3);
        //3*3方块的开始x轴坐标
        int regionCol = 3 * (x / 3);
        for (int i = 0; i < 9; i++) {
            //检查每一行的正确性
            if (board[i][x] == c) return false;
            //检查每一列的正确性
            if (board[y][i] == c) return false;
            //检查3*3方格内的正确性
            if (board[regionRow + i / 3][regionCol + i % 3] == c) return false;
        }
        return true;
    }

递归回溯分析整个矩阵

我们循环遍历整个二维数组,当遇到'.'后试探放入'1' ~ '9‘的整数并使用上面的方法进行正确性分析,如果正确后,递归调用下一个单元格,如果当前单元格放入'1' ~ '9'都不能得到合法的数独矩阵,则说明之前单元格放入了错误的值,此时回溯到上一次递归,试探放入下一个合法的整数,继续进行递归分析:

    /**
     * 递归函数,当得到合法的数据矩阵后返回true
     * 
     * @param board 数独矩阵
     * @param x x轴坐标代表列号
     * @param y y轴坐标代表行号
     * @return
     */
    private boolean check(char[][] board, int x, int y) {
        for (; y < 9; y++) {
            //如果输入的列号溢出,则直接进入下一行从第一列开始
            for (; x < 9; x++) {
                if (board[y][x] != '.') continue;
                //当前值为'.'时,
                //依次试探放入'1'~'9'的值来看是否满足合法的数独矩阵
                for (char c = '1'; c <= '9'; c++) {
                    //放入后不合法,直接忽略,尝试下一个值
                    if (!isValid(board, x, y, c)) continue;
                    board[y][x] = c;
                    //当前位置试探放入值c后,递归调用下一列
                    if (check(board, x + 1, y)) {
                        //当前值赋值c后,递归调用全部满足数独矩阵合法性,
                        //则返回成功
                        return true;
                    }
                    //当前位置放入c后,后续位置不能得到合法的数独矩阵,
                    //继续进行回溯分析,因为上次层递归调用要重写开始分析,
                    //所以要恢复当前的原始值
                    board[y][x] = '.';
                }
                //'1'~'9'全部试探完成后,
                //依然无法获得合法数独矩阵,
                //则说明之前放入的值有误,
                //返回后上次层递归方法继续取其他值进行递归
                return false;
            }
            //下一行,从第一列开始
            x = 0;
        }
        //此时数组已经越界,
        //说明全部递归完成没有发现非法数独矩阵,返回成功
        return true;
    }

验证

最后完成测试用例以及调用方法,进行结果验证:

    public void solveSudoku(char[][] board) {
        check(board, 0, 0);
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        char[][] 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'}
        };
        solution.solveSudoku(board);
        System.out.println("{");
        for (char[] row : board) {
            System.out.print("    {");
            String split = "";
            for (char c : row) {
                System.out.print(split + c);
                split = ",";
            }
            System.out.println("},");
        }
        System.out.println("}");
    }

得到正确结果:

{
    {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},
}

好了,以后这种数独问题我们都能在几毫秒内解决它了:)

你可能感兴趣的:(元宵节无法出门闹花灯,那么在家用程序解数独吧)