2.题目描述:
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空格内已填入了数字,空白格用 '.' 表示。
• 示例 1:
输入: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"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
•提示:
board.length == 9
board[i].length == 9
board[i][j] 是一位数字或者 '.'
题目数据 保证 输入数独仅有一个解
3. 解法:
算法思路:
为了存储每个位置的元素,我们需要定义一个二维数组。首先,我们记录所有已知的数据,然后遍历所有需要处理的位置,并遍历数字 1~9。对于每个位置,我们检查该数字是否可以存放在该位置,同时检查行、列和九宫格是否唯一。
我们可以使用一个二维数组来记录每个数字在每一行中是否出现,一个二维数组来记录每个数字在每一列中是否出现。对于九宫格,我们可以以行和列除以 3 得到的商作为九宫格的坐标,并使用一个三维数组来记录每个数字在每一个九宫格中是否出现。在检查是否存在冲突时,只需检查行、列和九宫格里对应的数字是否已被标记。如果数字至少有一个位置(行、列、九宫格)被标记,则存在冲突,因此不能在该位置放置当前数字。
•特别地,在本题中,我们需要直接修改给出的数组,因此在找到一种可行的方法时,应该停止递归,以防止正确的方法被覆盖。
初始化定义:
1. 定义行、列、九宫格标记数组以及找到可行方法的标记变量,将它们初始化为 false。2. 定义一个数组来存储每个需要处理的位置。
3. 将题目给出的所有元素的行、列以及九宫格坐标标记为 true。4. 将所有需要处理的位置存入数组。
递归函数设计:void dfs(vector<vector<char>>& board, int pos)参数:pos(当前需要处理的坐标);
返回值:无;
函数作用:在当前坐标填入合适数字,查找数独答案。
递归流程如下:
1. 结束条件:已经处理完所有需要处理的元素。如果找到了可行的解决方案,则将标记变量更新为 true 并返回。
2. 获取当前需要处理的元素的行列值。
3. 遍历数字 1~9。如果当前数字可以填入当前位置,并且标记变量未被赋值为 true,则将当前位置的行、列以及九宫格坐标标记为 true,将当前数字赋值给 board 数组中的相应位置元素,然后对下一个位置进行递归。
4. 递归结束时,撤回标记。
Java算法代码:
class Solution {
boolean [][] row,col;
boolean [][][] grid;
public void solveSudoku(char[][] board) {
row = new boolean[9][10];
col = new boolean[9][10];
grid = new boolean[3][3][10];
//初始化
for(int i = 0; i< 9;i++){
for(int j = 0; j < 9; j++){
if(board[i][j] != '.'){
int num = board[i][j] -'0';
row[i][num] = col[j][num] = grid[ i / 3][j / 3] [num] =true;
}
}
}
dfs(board);
}
public boolean dfs(char [][] board){
for(int i =0; i<9;i++){
for(int j =0;j<9;j++){
if(board[i][j]=='.'){
//填数字
for(int num = 1;num <= 9; num++){
if(!row[i][num] && !col[j][num] && !grid[i/3][j/3][num]){
board[i][j] = (char) ('0' + num);
row[i][num] = col[j][num] = grid[i/3][j/3][num] = true;
if(dfs(board) == true ) return true; //重点理解
//恢复现场
board[i][j] = '.';
row[i][num] = col[j][num] = grid[i/3][j/3][num] = false;
}
}
return false;//重点理解
}
}
}
return true;//重点理解
}
}
运行结果:
递归展开:
这里笔者其实都没有展开多少就理解,
先去看读者的上一个题,这里开始的初始化(就是先把数独的已经填数的来做标记,也就是说,这里已经填了一个数,那么这一行,这一列,这个九宫格,都不能再放这个数)
重点理解,几行返回值
还有递归调用的if
逻辑展开:
当细节都明白的时候,就会知道,数独求解,就是再已经标记好的数独上去遍历和尝试(进而寻找真正解,遇到错误解,就会回溯)。
---------------------------------------------------------------------------------------------------------------------------------
记住,相信你的递归函数,它可以做到!
记住,不理解时候,去尝试手动展开!
记住,逻辑展开(你不可能对所有的题目都进行手动展开)!