題目
主要考察:對深度優先搜尋和回溯的理解和基本功
題目描述
大家對數獨遊戲一定都不陌生:在一個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();
}
}