大家好,很久没更新了,今天写一写解数独的算法吧,这是一道leetcode困难级别的题目,本人的算法跟真正的大神相比效率一般,花费的时间和空间都很多。不过对于大多数人来说应该是最直观,最易懂的。
这道题目考虑的是最基本的数独,也就是每行,每列,每宫都是1到9。没有其他更加复杂的规则
题目当中数独是以一种二维字符矩阵(char[][])来呈现的。那么首先我们可以创立一个全局变量nums
char[] nums={'1','2','3','4','5','6','7','8','9'};
这样的话在之后的执行中可以直接引用字符,不需要考虑字符与整数之间的转换。
相信很多玩过数独的人都知道数独有两种基本的排除法。
1.当一个格子不可能是其他八个数的时候,那它一定就是剩下的那一个数。
2.当一个格子所在的行\列\宫当中其它空格都不可能是某一个数的时候,那么这个格子必然是这个数。(只要符合规则)
首先我们要先写出三条规则的判定方法。
//判断所在的行没有重复的数字
public boolean checkRow(char[][] board,int row,char val){
for(int i=0;i<9;i++){
if(board[row][i]==val){
return false;
}
}
return true;
}
//判断所在的列没有重复的数字
public boolean checkCol(char[][] board,int col,char val){
for(int i=0;i<9;i++){
if(board[i][col]==val){
return false;
}
}
return true;
}
//判断所在的宫没有重复的数字,这里判断格子所在的宫稍微有点难度
public boolean checkGrid(char[][] board,int row,int col,char val){
int start1=(row/3)*3; //找出左上角格子所在的行
int start2=(col/3)*3; //找出左上角格子所在的列
for(int i=start1;i<start1+3;i++){//锁定左上角格子,开始循环
for(int j=start2;j<start2+3;j++){
if(board[i][j]==val){
return false;
}
}
}
return true;
}
//判断是否满足所有三个条件
public boolean checkAll(char[][] board,int row,int col,char val){
return (checkRow(board,row,val)&&checkCol(board,col,val)&&
checkGrid(board,row,col,val));
}
实现了对规则的判断后,我们就可以找出一个空格里所有可能的数字。
public char[] allpossible(char[][] board,int row,int col){
String s="";
for(char k:nums){
if(checkAll(board,row,col,k)){
s+=k;
}
}
return s.toCharArray();
}
这样一来我们就基本实现了第一种排除法,接下来是第二种排除法。这种排除法比第一种要复杂,这里只解释行,其他的两个逻辑相似,不做赘述。
//判断所在行中其他空格都不能是一个数
public void rowonly(char[][] board,int row,int col){
for(char k:nums){
if(checkAll(board,row,col,k)){//首先如果要填入这个数字那就必须符合规则
for(int i=0;i<9;i++){
if(i!=col && board[row][i]=='.'){//找到所在行中其他的空格
if(checkCol(board,i,k) && checkGrid(board,row,i,k)){
//如果这个数可以被填入其他的空格,说明排除法不管用,直接结束运行
return;
}
}
}
//如果上述情况没发生,说明该空格只能填入这个数
board[row][col]=k;
return;
}
}
}
//判断所在列中其他空格都不能是一个数
public void colonly(char[][] board,int row,int col){
for(char k:nums){
if(checkAll(board,row,col,k)){
for(int i=0;i<9;i++){
if(i!=row && board[i][col]=='.'){
if(checkRow(board,i,k) && checkGrid(board,i,col,k)){
return;
}
}
}
board[row][col]=k;
return;
}
}
}
//判断所在宫中其他空格都不能是一个数
public void gridonly(char[][] board,int row,int col){
int start1=(row/3)*3;
int start2=(col/3)*3;
for(char k:nums){
if(checkAll(board,row,col,k)){
for(int i=start1;i<start1+3;i++){
for(int j=start2;j<start2+3;j++){
if(board[i][j]=='.' && (i!=row || j!=col)){
if(checkRow(board,i,k) && checkCol(board,j,k)){
return;
}
}
}
}
board[row][col]=k;
return;
}
}
}
实现了两种基本的排除法之后,我们就可以先执行这两种排除法,来尽快地填上一些空格。
public void simple_elimination(char[][] board){
boolean a=false;//这个布尔值能让我们不断地执行这个函数,不断地填入空格,直到基本的排除法已经不管用了为止
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]=='.'){
if(allpossible(board,i,j).length==1){//这是第一种排除法,即只有一个数能符合规则地填入该空格,则将其填入,继续循环
board[i][j]=allpossible(board,i,j)[0];
continue;
}
//如果该空格没被填上,执行第二种排除法
rowonly(board,i,j);
if(board[i][j]=='.'){//如果还没被填上,继续执行第二种排除法
colonly(board,i,j);
}
if(board[i][j]=='.'){//如果还没被填上,继续执行第二种排除法
gridonly(board,i,j);
}
if(board[i][j]!='.'){//如果被填上了,说明有进展,可以继续
a=true;
}
}
}
}
if(a){//继续执行简单的排除法直到无法填入新的数字
simple_elimination(board);
}
}
我们平时做的很多数独只用到这两种排除法就可以做完。当然对于更难的数独还有更高级的排除法,这里就不继续写了,毕竟我们是用计算机来解数独,暴力算法是可取的。我们之前用的两种排除法只是为了在较短的时间内填上尽可能多的格子,这样在指数级时间复杂度的暴力解法中可以大大减少花费的时间。
当然,接下来就是,最重要,也是最难的暴力解法。
public boolean hassolution(char[][] board){
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]=='.'){//考虑所有的空格
if(allpossible(board,i,j).length==0){//如果没有数字能填入,说明之前出了问题,返回false
return false;
}
else{
for(char k:allpossible(board,i,j)){
//尝试填入所有可能的数字
board[i][j]=k;
if(hassolution(board)){//如果新的数独有解,说明填对了
return true;
}
}
//当尝试过所有的数字都不能解开数独的时候,说明上一步填错了,需要把刚才的格子清空,然后返回错误,以便继续尝试其它的可能性
board[i][j]='.';
return false;
}
}
}
}
//默认状态为所有的格子都被填入数字,即数独已被解开,返回true
return true;
}
最后就可以解出来数独了
public void solveSudoku(char[][] board) {
simple_elimination(board);//先用简单排除法
boolean a=hassolution(board);//再用暴力解法
}
最后测试,先用简单排除法再用暴力解法比直接用暴力解法要节约一半的时间
本文介绍了如何使用Java实现解数独的算法,主要探讨了两种基础排除法:当一个格子只能是唯一数字时,以及当一个数字在行、列、宫中唯一时。通过这些方法,能有效简化暴力解法,降低时间复杂度。最终,结合暴力解法,成功解决数独题目。
935

被折叠的 条评论
为什么被折叠?



