例题:
9 x 9 方格如下图:
分析:
1、可以不断遍历每个未填的空格,逐一尝试1~9 若行、列、九宫格内没有冲突,则暂时填入。
2、一旦1~9 都尝试失败,回溯到上一次状态,换数字填入。
3、关键还是要记录冲突状态。
我们需要记录表格内每一行是否有冲突,每一行又有9个数字,需要知道这9个数字是否被使用过,被使用过的数字记为true,因此可以创建一个boolean型的二维数组来记录冲突。
简单理解:记录每一行 被使用过的数字。列冲突就是记录每一列 被使用过的数字。
比如定义一个数组ca[9][9]来记录行冲突状态,以第0行为例,第0行有数字1,2没有被使用,可以记录为false,其余数字记为true:
//ca[i] = {false, false, true, true, true, true, true, true, true}
数组的索引代表1~9这九个数,true代表该数字使用过。同理也可以创建一个列冲突。
接着创建九宫格冲突状态:需要找到每个小方格和九宫格的对应关系,i 表示二维表格的行,j 表示二维表格的列,第几个九宫格 = i / 3 * 3 + j / 3,例如第8行 第3列的小方格对应的九宫格为:
8 / 3 * 3 + 3 / 3 = 7 。 第7个九宫格。
代码实现:
import java.util.Arrays;
public class SudokuLeetcode37 {
public static void solveSudoku(char[][] table) {
/*
* 1.不断遍历每个未填的空格
* 逐一尝试 1 ~ 9 若行、列、九宫格内没有冲突,则填入
* 2.一旦 1 ~ 9 都尝试失败,回溯到上一次状态,换数字填入
* 3.关键还是要记录冲突状态
* */
//行冲突状态
boolean[][] ca = new boolean[9][9];
//ca[i] = {false, false, true, true, true, true, true, true, true}
//列冲突状态
boolean[][] cb = new boolean[9][9];
//cb[i] = {false, true, true, false, true, true, true, true, false}
//九宫格冲突
boolean[][] cc = new boolean[9][9]; //以7号九宫格为例
//cc[i/3*3 + j/3] = {true, false, true, true, true, true, false, true, true}
//遍历二维表格,初始化冲突状态
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char ch = table[i][j];
if (ch != '.') { //'5' --> 4
ca[i][ch - '1'] = cb[j][ch - '1'] = cc[i / 3 * 3 + j / 3][ch - '1'] = true;
}
}
}
//填数字
dfs(0, 0, table, ca, cb, cc);
}
//i,j表示向第i行,第j列填入数字
static boolean dfs(int i, int j, char[][] table, boolean[][] ca, boolean[][] cb, boolean[][] cc){
while(table[i][j] != '.'){ //查找下一个空格
if(++j >= 9){
j = 0;
i++;
}
if(i >= 9){//所有方格已填满,找到解了
return true;
}
}
for (int x = 1; x <= 9; x++) { //逐一尝试 1 ~ 9
//检查冲突
if(ca[i][x - 1] || cb[j][x - 1] || cc[i / 3 * 3 + j / 3][x - 1]){
continue;
}
//填入数字
table[i][j] = (char) (x + '0'); //1 --> 1 + '0'
//ca[0][0] = true 第0行不能储存1
//cb[2][0] = true 第0列不能储存1
//cc[0][0] = true 第0个九宫格不能储存1
//记录填入数字后的冲突
ca[i][x - 1] = cb[j][x - 1] = cc[i / 3 * 3 + j / 3][x - 1] = true;
if (dfs(i, j, table, ca, cb, cc)) {
return true;
}
table[i][j] = '.';
ca[i][x - 1] = cb[j][x - 1] = cc[i / 3 * 3 + j / 3][x - 1] = false;
}
return false;
}
public static void main(String[] args) {
char[][] table = {
{'5', '3', '.', '.', '7', '.', '.', '.', '.'},
{'6', '.', '.', '1', '9', '5', '.', '.', '.'},
{'.', '9', '8', '.', '.', '.', '.', '6', '.'},
{'8', '.', '.', '.', '6', '.', '.', '.', '3'},
{'4', '.', '.', '8', '.', '3', '.', '.', '1'},
{'7', '.', '.', '.', '2', '.', '.', '.', '6'},
{'.', '6', '.', '.', '.', '.', '2', '8', '.'},
{'.', '.', '.', '4', '1', '9', '.', '.', '5'},
{'.', '.', '.', '.', '8', '.', '.', '7', '9'}
};
solveSudoku(table);
print(table);
}
static char[][] solved = {
{'5', '3', '4', '6', '7', '8', '9', '1', '2'},
{'6', '7', '2', '1', '9', '5', '3', '4', '8'},
{'1', '9', '8', '3', '4', '2', '5', '6', '7'},
{'8', '5', '9', '7', '6', '1', '4', '2', '3'},
{'4', '2', '6', '8', '5', '3', '7', '9', '1'},
{'7', '1', '3', '9', '2', '4', '8', '5', '6'},
{'9', '6', '1', '5', '3', '7', '2', '8', '4'},
{'2', '8', '7', '4', '1', '9', '6', '3', '5'},
{'3', '4', '5', '2', '8', '6', '1', '7', '9'}
};
static void print(char[][] table) {
for (char[] chars : table) {
System.out.println(new String(chars));
}
System.out.println(Arrays.deepEquals(table, solved));
}
}
小结:
核心思想:
①:先要定义3个boolean型的二维数组,分别表示行冲突,列冲突和九宫格冲突。
②:遍历题目给出的填了部分数据的表格,在3个冲突数组里记录被使用过的数字,记为true(初始化冲突)。
③:先在表中找到空方格,然后向空方格填入数字,逐一尝试1~9(检查要填入的数字是否冲突),填入成功后记录填入数字后的冲突,如果1~9都不能填,则回溯到上一次的状态,重新填。没有解返回false(一般是有解的)。