编写一个程序,通过填充空格来解决数独问题。
数独的解法需遵循如下规则:
- 数字
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"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
/**
* @description: 递归回溯方法 TC:O(9^(9×9)) SC:O(n^2)
* @author: JunLiangWang
* @param {*} board
* @return {*}
*/
function recursion(board){
/**
* 该方案使用递归回溯的方式,首先遍历数独所有元素,并记录每行/列/3*3矩阵出现过的数。
* 而后利用递归遍历数独中的元素,如果遇到需要空白元素,则依次填入行/列/矩阵中未出现
* 过的数字(如果没有当前行/列/矩阵没有未出现过的数,则直接返回false)并记录该数已被
* 使用,而后递归下一个数独元素(直到递归完所有元素返回true)如果下一个递归结果为
* true证明该次填入数字正确,也返回true,反之返回false。
*/
// 记录行出现过的数字 9行9个数字,所有数组大小为[9*9]
let rowUseNumber=new Array(9).fill(0).map(()=>new Array(9).fill(false));
// 记录列出现过的数字 9列9个数字,所有数组大小为[9*9]
let columnUseNumber=new Array(9).fill(0).map(()=>new Array(9).fill(false));
// 记录3*3矩阵出现过的数字 9个3*3矩阵9个数字,所有数组大小为[9*9]
let metrixUseNumber=new Array(9).fill(0).map(()=>new Array(9).fill(false));
// 遍历数独,根据行/列/矩阵记录出现过的数字
for(let i=0;i<9;i++)
{
for(let j=0;j<9;j++)
{
// 取出数独数字
let number=board[i][j];
// 如果不为空,则记录数字
if(number!=='.')
{
// 记录i行的number数字为true,由于下标是0开始的,所以-1
rowUseNumber[i][number-1]=true;
// 记录j列的number数字为true,由于下标是0开始的,所以-1
columnUseNumber[j][number-1]=true;
// 第几个矩阵是由行和列决定的,其顺序为从上到下,从左到右
// 划分的9个矩阵,因此下标为Math.floor(i/3)*3+Math.floor(j/3)
metrixUseNumber[Math.floor(i/3)*3+Math.floor(j/3)][number-1]=true;
}
}
}
/**
* @description: 递归回溯,以每一行为基础,从上到下,从左到右递归数独中的元素
* 如果当前元素已填写数字,则返回递归下一个元素;如果当前元素未
* 填写数字,则遍历记录当前行/列/矩阵出现过的数字数组,找到未在
* 其中出现过的数,填入数独,并在记录该数已出现,以此继续递归数
* 独下一个元素,如果下一个元素返回true,证明该数填写正确,也返
* 回true,遍历完当前行/列/矩阵未出现过的数仍未找到填写正确的数
* 则返回false,直至递归完成数独所有元素结束
* @author: JunLiangWang
* @param {*} row
* @param {*} column
* @return {*}
*/
function recursionBacktrack(row,column){
// column如果超出了列的索引范围
if(column===9)
{
// 重置column为0
column=0;
// 并且换到下一行遍历
row++;
// 如果row超出了行的索引范围,则证明
// 所以数字填写完成,返回true即可
if(row===9)return true;
}
// 如果数独当前元素为空白,则需要填入元素
if(board[row][column]==='.')
{
// 遍历行/列/矩阵的九个数字
for(let i=0;i<9;i++)
{
// 如果行/列/矩阵当前数字都未被使用
if(!rowUseNumber[row][i]&&!columnUseNumber[column][i]&&!metrixUseNumber[
Math.floor(row/3)*3+Math.floor(column/3)][i])
{
// 将行/列/矩阵当前数字赋值为被使用
rowUseNumber[row][i]=true;
columnUseNumber[column][i]=true;
metrixUseNumber[Math.floor(row/3)*3+Math.floor(column/3)][i]=true;
// 将当前数字填入矩阵
board[row][column]=(i+1).toString();
// 继续递归下一个数独中下一数,如果后续全为true,证明当前数字填写
// 正确,返回true
if(recursionBacktrack(row,column+1))return true;
// 否则,重置刚刚赋值的元素继续遍历
rowUseNumber[row][i]=false;
columnUseNumber[column][i]=false;
metrixUseNumber[Math.floor(row/3)*3+Math.floor(column/3)][i]=false;
// 将当前数字填入矩阵
board[row][column]='.';
}
}
// 如果遍历完所有数都不满足,则返回false
return false;
}
// 否则递归遍历数独下一个数
else return recursionBacktrack(row,column+1);
}
// 执行递归
recursionBacktrack(0,0);
// 返回结果
return board;
}