题目:解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3×3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
输入:board = [["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"]]
输出:[["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"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sudoku-solver
提示:
board.length == 9
board[i].length == 9
board[i][j] 是一位数字或者 '.'
题目数据 保证 输入数独仅有一个解
理解题意
这道题和36题相比,我们已经知道了,行列 块索引,多了一步就是需要填写数独,且已经保证数独只有唯一的解了,所以我们不需要在判断数独是否是个有效的了,现在需要的是把数字填进去,并且保证 行/列/块 中的数字不能重复。
1.想法
我猜想:还是使用hashmp存放数值,根据36题的方式写,又想到,当这个空格填入一个值之后,后面可能再填这个值,那么就会出错,可能需要不断的试探,难道用一个while循环?
看了下评论和题解,本题使用到的是递归和回溯。
先填入一个值,循环再填入一个值,当发现没有值可以填时,再向前回溯,重新填入值。
我没有写出来。就看了官方的结题思路:
- 定义行 列 块的布尔数组,定义一个list,为了存放没有填的值‘.’的坐标
- 遍历这个board,如果是‘.’,list就存入坐标,如果有值,就把他们的布尔数组设置为true;
- 定义回溯的方法:先写终止条件:当填入的数字个数等于 ‘.’的个数时 终止
- 拿出‘.’的坐标,循环数字1-9填入,递归,然后在进行回溯,看看能不能满足条件。
将代码附上:
class Solution {
//行 列 3*3的格子是否会重复
private boolean[][] row = new boolean[9][9];
private boolean[][] col = new boolean[9][9];
private boolean[][][] block = new boolean[3][3][9];
boolean valid =false;
List<int[]> list =new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for(int i = 0 ; i< 9 ; i++)
{
for(int j = 0 ; j < 9 ; j++)
{
if(board[i][j]=='.')
{
list.add(new int[]{i,j});//把这个空坐标 添加到list中;
}
else
{
int num = board[i][j]-'0'-1;//获取到这个位置的数字;
//因为是9*9 出现的那个数字应该是独一无二的 -1表示这个数字 在数组中出现的位置的数字
row[i][num] = col[j][num] = block[i/3][j/3][num] = true;
}
}
}
dfs(board,0);
}
public void dfs(char[][] board,int pos)
{
if(pos ==list.size())//就是当填入的数字等于 开始空出来的数字的时候就可以返回true了
{
valid = true;
return;
}
int[] li = list.get(pos);//获取到那个 空的值 一个行 一个列
int i = li[0]; int j=li[1];
//将1-9循环填入这个数字 看行不行
for(int digit = 0 ; digit<9&& !valid;digit++)
{
if(!row[i][digit]&&!col[j][digit]&&!block[i/3][j/3][digit])
{
row[i][digit] =col[j][digit]=block[i/3][j/3][digit]=true;
board[i][j] = (char) (digit+'0'+1);
dfs(board,pos+1);
row[i][digit] = col[j][digit]=block[i/3][j/3][digit] = false;
}
}
}
}
在写的过程中,需要注意的是
for(int digit = 0 ; digit<9&& !valid;digit++)
其中的valid不能省略,valid标记的是这个地方有没有走过。当我编程吧valid去掉后就出错了。还需要注意的是board是char类型的,转换需要注意。
总结
开始的时候不太会写代码,想了一会儿写不出,就看了答案,答案好像也不是很懂,就照着答案写了一遍,写了一遍之后,再仔细看各个步骤的意思,就能够懂一些了,其中使用的回溯法,当不满足时,就将当前在设置为false,这种回溯方法在之前就遇到过, 虽然知道使用回溯方法,但是遇到问题的时候却不会使用了,以后需要强制自己进行此类问题的思考才行,不能太依赖答案呀。还有我看答案第二个方法使用了位运算来解,和上面思想有点相似,不同的是上面是用布尔类型,位运算代替了布尔类型来进行存储,我看了下,没有看懂的地方在哪些位运算符上,所以这里就没有列出了,先把这些题做出来,然后再完善吧。希望我能记住本题是怎么回溯的。