题目
主要考察:对深度优先搜索和回溯的理解和基本功
题目描述
大家对数独游戏一定都不陌生:在一个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();
}
}