日行一算(數獨遊戲)

dawa大娃bigbaby發表於2022-02-17

題目

主要考察:對深度優先搜尋和回溯的理解和基本功

題目描述
大家對數獨遊戲一定都不陌生:在一個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();
    }
}

相關文章