日行一算(数独游戏)

题目

主要考察:对深度优先搜索和回溯的理解和基本功

题目描述
大家对数独游戏一定都不陌生:在一个99的方阵上划分出9个更小的33的子方阵。每个格子上填1到9这9个数字中的一个。有些格子上的数字确定了,不能再改动。要求把剩余尚未确定的地方填上适当的数字并且保证:
1.任意一行的9个数字均不相同
2.任意一列的9个数字均不相同
3.任意子方阵中的9个数字均不相同。

解答要求
时间限制:1000ms, 内存限制:100MB
输入
每个输入文件一共9行,每行9个数字(0到9),分别表示该行9个位置上填的数。0表示该位置上的数字尚未确定,需要由您来确定,1到9表示该位置上的数字已经确定,不能再改动。(所提供的输入保证有且仅有一种解法)

输出
共9行,输出数独的填数方法。

样例
输入样例

103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107

输出样例

143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127

解题思路

一开始直接使用暴力回溯,会超时到两秒。代码如下所示。

import java.util.Scanner;
public class Main {
    static int matrix[][] = new int[11][11];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int i = 1; i <= 9; ++i) {
            String input[] = sc.nextLine().split("");
            for (int j = 1; j <= 9; ++j) {
                matrix[i][j] = Integer.valueOf(input[j - 1]);
            }
        }
        backTrace(1, 1);
    }
    // 回溯
    private static void backTrace(int i, int j) {
        if (i == 9 && j == 10) {
            // 已经成功了,打印数组即可
            printArray();
            return;
        } else if (j == 10) { // 已经到了列末尾了,还没到行尾,就换行
            i++;
            j = 1;
        }
        // 如果i行j列是空格,那么才进入给空格填值的逻辑
        if (matrix[i][j] == 0) {
            for (int k = 1; k <= 9; k++) {
                // 判断给i行j列放1-9中的任意一个数是否能满足规则
                if (check(i, j, k)) {
                    // 将该值赋给该空格,然后进入下一个空格
                    matrix[i][j] = k;
                    backTrace(i, j + 1);
                    // 初始化该空格
                    matrix[i][j] = 0;
                }
            }
        } else {
            // 如果该位置已经有值了,就进入下一个空格进行计算
            backTrace(i, j + 1);
        }
    }
    // 检查
    private static boolean check(int row, int col, int number) {
        // 判断该行该列是否有重复数字
        for (int i = 1; i <= 9; i++) {
            if (matrix[row][i] == number || matrix[i][col] == number) {
                return false;
            }
        }
        // 判断小九宫格是否有重复
        int tempRow = (row - 1) / 3;
        int tempLine = (col - 1) / 3;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (matrix[tempRow * 3 + i + 1][tempLine * 3 + j + 1] == number) {
                    return false;
                }
            }
        }
        return true;
    }
    /**
     * 打印矩阵
     */
    public static void printArray() {
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= 9; j++) {
                System.out.print(matrix[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

因此需要将代码合理剪枝,在找到目的数独结果之后,停止搜索。


    static boolean flag = false;
    // 回溯
    private static void backTrace(int i, int j) {
        if (i == 9 && j == 10) {
            // 已经成功了,打印数组即可
            printArray();
            flag = true;
            return;
        } else if (j == 10) { // 已经到了列末尾了,还没到行尾,就换行
            i++;
            j = 1;
        }
        // 剪枝,避免找到之后继续迭代
        if (flag) {
            return;
        }
        // 如果i行j列是空格,那么才进入给空格填值的逻辑
        if (matrix[i][j] == 0) {
            for (int k = 1; k <= 9; k++) {
                // 判断给i行j列放1-9中的任意一个数是否能满足规则
                if (check(i, j, k)) {
                    // 将该值赋给该空格,然后进入下一个空格
                    matrix[i][j] = k;
                    backTrace(i, j + 1);
                    // 初始化该空格
                    matrix[i][j] = 0;
                }
            }
        } else {
            // 如果该位置已经有值了,就进入下一个空格进行计算
            backTrace(i, j + 1);
        }
    }

该种情况下,依然会有超时的情况发生,不过时间已经从两秒降到了1.2S,此时距离目标1S已经很接近了。由于我们知道在不使用其他算法,仅仅回溯算法的情况下,如果想要进一步压缩时间的话,就只能在探测是否重复的地方进行优化。因为该步骤会导致我们再迭代18次进行检查。

如果在这里使用空间换取时间进行优化的话,我们就能将O(18N3)的算法压缩成O(N3),此时应该能够将时间进行进一步压缩。

最终的AC代码如下,运行时间0.8s:

import java.util.Scanner;
public class Main {
    static int matrix[][] = new int[11][11];
    static boolean flag = false;
    static boolean col[][] = new boolean[11][11];
    static boolean row[][] = new boolean[11][11];
    static boolean zheng[][] = new boolean[11][11];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int i = 1; i <= 9; ++i) {
            String input[] = sc.nextLine().split("");
            for (int j = 1; j <= 9; ++j) {
                matrix[i][j] = Integer.valueOf(input[j - 1]);
                row[i][matrix[i][j]] = true;
                col[j][matrix[i][j]] = true;
                zheng[(i - 1) / 3 * 3 + (j - 1) / 3 + 1][matrix[i][j]] = true;
            }
        }
        backTrace(1, 1);
    }
    // 回溯
    private static void backTrace(int i, int j) {
        if (i == 9 && j == 10) {
            // 已经成功了,打印数组即可
            printArray();
            flag = true;
            return;
        } else if (j == 10) { // 已经到了列末尾了,还没到行尾,就换行
            i++;
            j = 1;
        }
        // 剪枝,避免找到之后继续迭代
        if (flag) {
            return;
        }
        int zhengIndex = (i - 1) / 3 * 3 + (j - 1) / 3 + 1;
        // 如果i行j列是空格,那么才进入给空格填值的逻辑
        if (matrix[i][j] == 0) {
            for (int k = 1; k <= 9; k++) {
                // 判断给i行j列放1-9中的任意一个数是否能满足规则
                if ((!row[i][k]) && (!col[j][k]) && (!zheng[zhengIndex][k])) {
                    // 将该值赋给该空格,然后进入下一个空格
                    matrix[i][j] = k;
                    row[i][k] = true;
                    col[j][k] = true;
                    zheng[zhengIndex][k] = true;
                    backTrace(i, j + 1);
                    row[i][k] = false;
                    col[j][k] = false;
                    zheng[zhengIndex][k] = false;
                    // 初始化该空格
                    matrix[i][j] = 0;
                }
            }
        } else {
            // 如果该位置已经有值了,就进入下一个空格进行计算
            backTrace(i, j + 1);
        }
    }
    /**
     * 打印矩阵
     */
    public static void printArray() {
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= 9; j++) {
                System.out.print(matrix[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}
posted @ 2022-02-17 10:00  dawa大娃bigbaby  阅读(203)  评论(5编辑  收藏  举报