37. 解数独
题目描述
解题思路
引用自代码随想录对应题的图辅助理解
代码
《backTracking递归函数》
递归函数以及传入参数:
返回值需要设置为boolean【找到一个方案(就在树的叶子节点上)立刻就返回,即找从根节点到叶子节点一条唯一路径,而不像其他普遍的回溯算法问题需要全部解所以是void】
终止条件【在下面的递归单层搜索逻辑中包含了:无解情况和自然填满棋盘情况】:
解数独的过程意味着遍历整个树形结构寻找可能的叶子节点就立刻返回【不需要终止条件是因为递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止】
递归单层搜索逻辑:
需要二维递归【一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的哪一个】【行用board.length,列用board[i].length【相当于列的总数量是随便取一个棋盘的行的长度】】
检查是否是'.'如果是再进行下一步逻辑【如果是空格则开始试9个数】
循环9个数【注意放的是字符char】:先检查是否合法,如果合法则把k放进棋盘的坐标中(board[i][j] = k)
放完了当前坐标以后需要放下个坐标了,也就是下一层递归了【注意这里不像普遍的回溯算法的写法,由于递归函数会返回boolean因此相当于一系列递归完之后,如果board的其他位置都被放置完毕则返回true】(if backTracking(board) return true)【如果递归函数没有boolean返回值则for循环遍历棋盘会永远进行下去,也因此反证需要boolean类型作为返回值】
记得加return false【这样代表着当一行一列确定下来了,尝试了9个数都不行,说明这个棋盘无解,因此会直接返回,即没有终止条件也不会因为填不满棋盘导致无限递归】【和循环9个数的for循环缩进同级是因为如果9个数尝试完都不行】
return true和最外层循环同级【因为return false在if里面,而这个if是填入棋盘中空格,如果没有false说明棋盘中没有空格所以不会执行if】
《isValid判断棋盘是否合法的函数》
传入参数:
i, j(坐标), k(放入的字符), board(当前棋盘)
逻辑:
同行、同列、同九宫格是否存在重复的情况
如果同行、同列、同九宫格中现有坐标有等于放入的字符则返回false
class Solution {
public void solveSudoku(char[][] board) {
backTracking(board);
}
// 只需要找到一个解就可以返回,因此返回值是boolean而不是普遍回溯问题设置的void
private boolean backTracking(char[][] board) {
// 不需要终止条件:因为终止条件其实是填满了棋盘则自动终止,蕴含在递归单层逻辑中
// 递归单层逻辑:
// 二维递归:遍历行和列
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
// 行列确定下来后只处理空格的位置
if (board[i][j] == '.') {
// 依次试9个数
for (char k = '1'; k <= '9'; k++) {
// 先检查是否合法:合法再放入数
if (isValid(i, j, k, board)) {
board[i][j] = k;
// 放完了之后接下来继续放置其他位置【也就是进行后续递归遍历】
if (backTracking(board)) return true;
// 回溯
board[i][j] = '.';
}
}
// 9个数都试完了,如果不行则说明无解
return false;
}
}
}
// 遍历完棋盘所有的位置,都没有空格说明解完了
return true;
}
private boolean isValid(int row, int col, char val, char[][] board) {
int n = board.length;
// 同行不能有重复
for (int j = 0; j < n; j++) if (board[row][j] == val) return false;
// 同列不能有重复
for (int i = 0; i < n; i++) if (board[i][col] == val) return false;
// 同一个九宫格里不能有重复
int sqrtN = (int) Math.sqrt(n);
int startRow = (row / sqrtN) * sqrtN;
int startCol = (col / sqrtN) * sqrtN;
for (int i = startRow; i < startRow + sqrtN; i++) {
for (int j = startCol; j < startCol + sqrtN; j++) {
if (board[i][j] == val) return false;
}
}
// 其余情况则可以
return true;
}
}