zoukankan      html  css  js  c++  java
  • Java实现的利用递归和回溯解决Leetcode一道hard难度题

    打卡Leetcode每日一题 37. 解数独

    题目

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

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

    空白格用.表示。

    image-20200915163234076image-20200915163253830

    思路

    根据题干中给出的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();
            }
        }
    }
    

    测试结果

    image-20200915171553684

  • 相关阅读:
    mysql数据库常用指令
    解决windows的mysql无法启动 服务没有报告任何错误的经验。
    “Can't open file for writing”或“operation not permitted”的解决办法
    启动Apache出现错误Port 80 in use by "Unable to open process" with PID 4!
    如何打开windows的服务services.msc
    常见的HTTP状态码 404 500 301 200
    linux系统常用的重启、关机指令
    (wifi)wifi移植之命令行调试driver和supplicant
    linux(debian)安装USB无线网卡(tp-link TL-WN725N rtl8188eu )
    alloc_chrdev_region申请一个动态主设备号,并申请一系列次设备号
  • 原文地址:https://www.cnblogs.com/chiaki/p/13674178.html
Copyright © 2011-2022 走看看