题目
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
算法
使用递归算法, 首先应获取第一个要填充的的位置, 即行和列, 优先处理候选值比较少的, 提升效率, 因为如果复杂度呈指数增长. 填充完毕后应判断是否正确, 如果不正确返回为空.
import java.util.HashSet;
import java.util.Set;
/**
* Created by GuanDS on 2018/8/28.
*/
public class Test {
public static void main(String[] args) {
char[][] board = {
{8, '.', '.', '.', '.', '.', '.', '.', '.'},
{'.', 7, '.', '.', 9, '.', 2, '.', '.'},
{'.', '.', 3, 6, '.', '.', '.', '.', '.'},
{'.', 5, '.', '.', '.', 7, '.', '.', '.'},
{'.', '.', '.', '.', 4, 5, 7, '.', '.'},
{'.', '.', '.', 1, '.', '.', '.', 3, '.'},
{'.', '.', 1, '.', '.', '.', '.', 6, 8},
{'.', '.', 8, 5, '.', '.', '.', 1, '.'},
{'.', 9, '.', '.', '.', '.', 4, '.', '.'}
};
board = fill(board);
if (board == null) {
System.out.println("数独无解");
return;
}
System.out.printf("[");
for (int j = 0; j < board.length; j++) {
System.out.printf("[\"");
for (int i = 0; i < board[0].length; i++) {
System.out.print((int) board[j][i] + (i == board.length - 1 ? "\"]" : "\", \""));
}
if (j != board.length - 1) {
System.out.printf(", ");
}
}
System.out.printf("]");
}
// 递归调用填充
private static char[][] fill(char[][] board) {
int[] index = fillIndex(board);
int i = index[0], j = index[1];
if (i < 0 || j < 0) {
return board;
}
//统计是否有重复字符
Set<Character> setX = new HashSet<>();
Set<Character> setY = new HashSet<>();
for (int m = 0; m < board.length; m++) {
if (board[j][m] != '.' && !setX.add(board[j][m])) {
return null;
}
}
for (int n = 0; n < board.length; n++) {
if (board[n][i] != '.' && !setY.add(board[n][i])) {
return null;
}
}
// 合并行和列的每个不同字符, 找到未使用的字符
setX.addAll(setY);
if (setX.size() >= board.length) {
return null;
}
for (int k = 1; k < board.length + 1; k++) {
// 每个不存在的字符去做尝试
if (!setX.contains((char) k)) {
char[][] temp = copy(board);
temp[j][i] = (char) k;
char[][] result = fill(temp);
if (result != null) {
return result;
}
}
}
return null;
}
// 查找数组中候选数最少的一个, 优先选择, 提升效率
private static int[] fillIndex(char[][] array) {
int max = 0, maxI = -1, maxJ = -1;
for (int j = 0; j < array.length; j++) {
for (int i = 0; i < array.length; i++) {
if (array[j][i] != '.') {
continue;
}
Set<Character> set = new HashSet<>();
for (int m = 0; m < array.length; m++) {
if (array[m][i] != '.') {
set.add(array[m][i]);
}
}
for (int n = 0; n < array.length; n++) {
if (array[j][n] != '.') {
set.add(array[j][n]);
}
}
if (set.size() > max) {
max = set.size();
maxI = i;
maxJ = j;
}
if (max == array.length - 1) {
return new int[]{i, j};
}
}
}
return new int[]{maxI, maxJ};
}
// 复制数组
private static char[][] copy(char[][] array) {
char[][] result = new char[array.length][array[0].length];
for (int m = 0; m < array.length; m++) {
for (int n = 0; n < array[0].length; n++) {
result[m][n] = array[m][n];
}
}
return result;
}
}
结果
[["8", "3", "7", "2", "1", "6", "9", "4", "5"], ["1", "7", "4", "3", "9", "8", "2", "5", "6"], ["9", "8", "3", "6", "2", "1", "5", "7", "4"], ["3", "5", "2", "4", "6", "7", "1", "8", "9"], ["2", "1", "6", "8", "4", "5", "7", "9", "3"], ["4", "6", "9", "1", "5", "2", "8", "3", "7"], ["5", "2", "1", "9", "7", "4", "3", "6", "8"], ["7", "4", "8", "5", "3", "9", "6", "1", "2"], ["6", "9", "5", "7", "8", "3", "4", "2", "1"]]