编程语言
首页 > 编程语言> > 【算法刷题】解数独

【算法刷题】解数独

作者:互联网

本文为个人解题思路整理,水平有限,有问题欢迎交流


概览

本题已数独问题为背景,要求计算出唯一解,表面是一个暴力深搜和回溯的问题,然而实际上如何优化才是精华所在

难度:中等

核心知识点:DFS(回溯)、状态压缩、位运算


题目来源

力扣:https://leetcode-cn.com/problems/sudoku-solver


题目内容

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

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

本题满足以下设定:

样例

源数据

img

结果

img

解题思路


解题思路确定,开始整理解题方案


解题方案

  1. 遍历整个棋盘的每个点,以计算行、列、块的状态,并获取

    • 若该点为.,证明为空白,将这个点存储在列表中
    • 若该点不位.,证明为数字,将相关的行、列、块中标记已出现过这个数字
  2. 开始深度搜索

    1. 获取选择可能性最小的点position,并计算其可能的所有数字

    2. 检查position是否存在,不存在则证明所有空点已填充,修改标记为搜索成功,并结束递归

    3. 用数字i枚举1-9

      1. 检查是否是否允许数字i,不允许则跳过

      2. 修改数据

        1. 将当前搜索的点修改为数字i

        2. 修正与当前相关的行、列、块的状态

        3. 将当前点标记为已填充

      3. 进行下一层搜索

      4. 检查搜索成功的标记,若搜索成功则结束递归

      5. 撤回修改数据

        1. 将当前搜索的点修改为空白.

        2. 修正与当前相关的行、列、块的状态

        3. 将当前点标记为未填充


完整代码

class DemoBasicApplicationTests {
    @Test
    void test() {
        char[][] board = {
                {'5', '3', '.', '.', '7', '.', '.', '.', '.'},
                {'6', '.', '.', '1', '9', '5', '.', '.', '.'},
                {'.', '9', '8', '.', '.', '.', '.', '6', '.'},
                {'8', '.', '.', '.', '6', '.', '.', '.', '3'},
                {'4', '.', '.', '8', '.', '.', '.', '.', '1'},
                {'7', '.', '.', '.', '2', '.', '.', '.', '6'},
                {'.', '6', '.', '.', '.', '.', '2', '8', '.'},
                {'.', '.', '.', '4', '1', '9', '.', '.', '5'},
                {'.', '.', '.', '.', '8', '.', '.', '7', '9'}
        };
        solveSudoku(board);
    }

    public int[] col = new int[9];//行
    public int[] row = new int[9];//列
    public int[][] block = new int[3][3];//块
    List<Integer> list = new ArrayList<>();//空白位列表
    boolean flag = false;//标记,用于识别搜索是否成功

    /**
     * 解决方案
     */
    public void solveSudoku(char[][] board) {
        //初始化
        init(board);
        //执行dfs搜索
        dfs(board);
        //打印结果
//        System.out.println(flag);
        out(board);
    }

    /**
     * 打印board
     * 调试用
     *
     * @param board 目标board
     */
    public void out(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();
        }
        System.out.println();
    }

    /**
     * 初始化,填充行、列、块以及空白位列表的值
     *
     * @param board 目标board
     */
    public void init(char[][] board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    //不为空的时候,更新行、列、块的值
                    update(i, j, board);
                } else {
                    //为空,则将其添加到空白位列表
                    list.add(i * 9 + j);
                }
            }
        }
    }

    /**
     * 执行搜索
     * 在排除所有可能后结束搜索,flag标记搜索成功或失败
     *
     * @param board 目标board
     */
    private void dfs(char[][] board) {
        //查询到list中下一个位置
        int nextPosition = getNextPosition();
        //找不到下一个尝试位置,即所有点均填充完成,则结束搜索,并判断为搜索成功
        if (nextPosition < 0) {
            flag = true;
            return;
        }
        //下一个位置的棋盘中的位置
        int next = list.get(nextPosition);
        int state = getState(next);
        //从1开始检索,检索到9
        int i = 0;
        while (++i < 10) {
            //开始尝试
            if ((state >> i) % 2 == 0) {//第i位为0,则证明该位置可能为i
                //更新行列
                board[next / 9][next % 9] = (char) (i + '0');
                list.set(nextPosition, -1);
                update(next / 9, next % 9, board);
//                out();
//                System.out.println("" + next / 9 + "    " + next % 9 + "    " + i);
                //开始搜索下一个位置
                dfs(board);
                //找到答案,结束搜索
                if (flag) {
                    return;
                }
                //未找到答案,撤回修改,继续尝试
                board[next / 9][next % 9] = '.';
                list.set(nextPosition, next);
                update(next / 9, next % 9, i, board);
            }
        }
    }

    /**
     * 获取下一个位置
     *
     * @return 下一个位置在list中的位置,若结果为-1则证明没有需要查询的结果
     */
    private int getNextPosition() {
        int position = -1;
        int minNum = -1;
        for (int i = 0; i < list.size(); i++) {
            //忽略被标记为-1的位置
            if (list.get(i) >= 0) {
                //找到可能性最少的位置
                int possibleNum = getPossibleNum(list.get(i));
                if (position < 0 || possibleNum < minNum) {
                    minNum = possibleNum;
                    position = i;
                }
            }
        }
        return position;
    }

    /**
     * 计算可能数字的数量
     */
    private int getPossibleNum(int position) {
        int state = getState(position);
        int num = 0;
        //遍历每个二进制位,若为1则计数器加1
        while (state > 0) {
            num += state & 1;
            state >>= 1;
        }
        return 9 - num;
    }

    /**
     * 查询某个位置的状态
     */
    private int getState(int position) {
        int x = position / 9;
        int y = position % 9;
        int state = col[x] | row[y] | block[x / 3][y / 3];
        return state;
    }

    /**
     * 更新某个位置的数据
     *
     * @param x     横坐标
     * @param y     纵坐标
     * @param board 棋盘
     */
    private void update(int x, int y, char[][] board) {
        int num = 1 << (board[x][y] - '0');
        col[x] |= num;
        row[y] |= num;
        block[x / 3][y / 3] |= num;
    }

    /**
     * 更新某个位置的数据到指定数字
     *
     * @param x      横坐标
     * @param y      纵坐标
     * @param target 目标数字
     * @param board  棋盘
     */
    private void update(int x, int y, int target, char[][] board) {
        int num = 1 << target;
        col[x] ^= num;
        row[y] ^= num;
        block[x / 3][y / 3] ^= num;
    }
}

执行结果:

image-20200916163654701

性能:

image-20200916164057937

记得关闭掉打印,否则会影响执行时间


后记

表面深搜,实则优化,提出解决方案并不难,提出优质的解决方案才是我们该追求的



作者:Echo_Ye

WX:Echo_YeZ

Email :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

标签:int,state,next,算法,搜索,board,position,解数,刷题
来源: https://www.cnblogs.com/silent-bug/p/13680052.html