• 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

  • 相关阅读:
    关于两次fork
    阻塞非阻塞与同步异步的区别
    函数调用堆栈
    数组
    64位操作系统与32位操作系统数据类型的字节数
    Redis 键(key)
    Redis 命令
    Redis 安装
    Redis 配置
    MongoDB 安装
  • 原文地址:https://www.cnblogs.com/chiaki/p/13674178.html
Copyright © 2020-2023  润新知