数独原来如此简单

245 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情

有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

image.png

如何判断这个 9X9 数独是有效的?

每一行称为数独的,每一列称为数独的,每一个小九宫格称为数独的。数独的基本规则就是每一行、每一列、每一宫中,1-9这9个数字都只出现一次。

用(行,列)表示上图的单元格,例如(1,1)表示第一行第一列的单元格,(2,4)表示第二行第四列的单元格

如上图,每个空白单元格中能填的数字都是有限制的。

首先:数独的每一行数字不重复,且为1-9内的数据,每一列是1-9数字不重复,对应的小九宫格子也不能重复。

比如 image.png

这个部分的格子:我们按照行列不重复,方块内不重复原则判断

(1,3)能填的只有:1 2 4 (2,2)能填的有2 4 7 (2,3)能填的有2 4 7 (3,1)能填的有1 2

那么这个方格答案有 image.png image.png image.png image.png等。

我们选择一个解法,比如第一个图,然后得到:

image.png

然后按照前面一个图的方法,继续填充,得到最后结果,当然,如果第一个方格不正确,会导致最好得到错误结果。

但是在本题里面,我们可以只需要判断有数字的部分,在本题里面。判断(1,1)的行列方格,发现没有存在重复,是准确的,继续判断(1,2)......

如何使用代码???

那么我们可以对每一个数进行遍历,如果当前没有数字,我们遍历下一个,如果当前值等于i,那么判断i对应行,列,方格是不是有对应的值

存储数独我们使用二维数组。

Scanner sc=new Scanner(System.in);
char[][] board=new char[9][9]
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
board[i][j]=sc.nextcharact();
}
}

怎么判断数组? 进行二重循环

for (int i = 0; i < length; ++i)
            for (int j = 0; j < length; ++j) {
                //如果还没有填数字,直接跳过
                if (board[i][j] == '.')
                    continue;
              // todo
            }

在todo那块怎么写??? 我们需要定义一个行,列的判断,这里

        int line[][] = new int[length][length];
        int column[][] = new int[length][length];
        int cell[][] = new int[length][length];

那么todo部分:

当前数据:int num = board[i][j] - '0' - 1;

当前列:column[num][i]

当前行:line[i][num]

当前方格:

如何判断方格?我们重点九宫格,那么可以定义一个值,标识第几个方格,9宫格数独横着和竖着都是3个单元格 int k = i / 3 * 3 + j / 3;

方格:ceil[k][num]

当前行列方格对应值不为0,说明第i(i从0开始)行有num这个数字。返回false

所以完整代码如下:

class Solution{
        public boolean isValidSudoku(char board[][]) {
        int length = board.length;
        int line[][] = new int[length][length];
        int column[][] = new int[length][length];
        int cell[][] = new int[length][length];
        for (int i = 0; i < length; ++i)
            for (int j = 0; j < length; ++j) {
                if (board[i][j] == '.')
                    continue;
                int num = board[i][j] - '0' - 1;
                int k = i / 3 * 3 + j / 3;
                if (line[i][num] != 0 || column[j][num] != 0 || cell[k][num] != 0)
                    return false;
                line[i][num] = column[j][num] = cell[k][num] = 1;
            }
        return true;
    }
}

那么除了判断九宫格有没有效,能不能自动填充九宫格,生成答案呢?

如何进行九宫格填空

我们先做 3X3 格子的九宫格

需要记住横、竖、斜三个格子的数字和只能为15

先定义一个3x3数组,然后通过循环添加值

public class NineTable {
  public static void main(String[] args) {
    int arr[][] = new int[3][3];
    int a = 2;
    int b = 3 / 2;
    for (int i = 1; i <= 9; i++) {
      arr[a++][b++] = i;
      if (0 == i % 3) {
        a = a - 2;
        b = b - 1;
      }
      else {
        a = a % 3;
        b = b % 3;
      }
    }
    System.out.println("output:");
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        System.out.print(arr[i][j] + " ");
      }
      System.out.print("\n");
    }
  }
}

口诀:戴九履一,左三右七,二四有肩,八六为足,五居中央。

九宫格的要求是在上面填写的数字,做到行,列,对角线之和相等,并且数字不能相等,所以我们在做的时候可以运用这个口诀:2,4为肩;6,8为足;上9下1;左7右3,也就是294 753 618。

  1. 首先生成9x9x9的三维数组,每个值为[1,2,3,4,5,6,7,8,9] 
  2. 然后确定有独一的值,清除行,列,块当中的值 clean(data)函数 
  3. 继续1 
  4. 然后找到唯一值,即行、列或块当中只出现一次的值,设置当前值,然后执行1,clean_help(data)函数 
  5. 最后如果没有唯一值那就找最少候选的那个格子先尝试这个格子的其中一个候选,再尝试剩余的候选数字 

9X9

布尔数组row,给它九行九列,让row[i][number]来表示i+1行是否出现过number+1【注意计算机的数组下标从零开始,当然你可以根据习惯舍弃第0行列而从1开始】。

    布尔数组col,给它九行九列,让col[i][number]来表示i+1列是否出现过number+1【注意计算机的数组下标从零开始,当然你可以根据习惯舍弃第0行列而从1开始】。

    布尔数组mid,给它九行九列,让mid[i][number]来表示i+1个小九宫是否出现过number+1【注意计算机的数组下标从零开始,当然你可以根据习惯舍弃第0行列而从1开始】。

函数原型:f(i,j),表示当前尝试的元素是i+1行、j+1列的

1.设置哨兵[布尔类型],作为求解结束的标志,即得出一个结果时,通知递归函数跳出。

2.当尝试的行列超出了9,说明求出结果了,哨兵至true,跳出递归。

3.当前元素已经填过了,那就跳过它,尝试下一个元素

4.当前元素没有填过,将可能的值填入,并更新表示已填的那三个布尔数组,尝试下一个元素。如果尝试完成后问题解决了,就退出递归函数,否则就重置刚刚的状态:包括局面和三个布尔数组。

第一步:定义变量:

    final int ROW=9;
	final int COL=9;
        //行列方格
	boolean [][]row=new boolean[ROW][COL];
	boolean [][]col=new boolean[ROW][COL];
	boolean [][]mid=new boolean[ROW][COL];
	
	char[][] board;
	boolean solve=false;

第二步初始化数独

public void solveSudoku(char[][] board) {
	for(int i=0;i<ROW;i++) {
           for(int j=0;j<COL;j++) {
	      if(board[i][j]=='.') {
                  continue;
	      }else {
		row[i][board[i][j]-'0'-1]=true;
		col[j][board[i][j]-'0'-1]=true;
		mid[i/3*3+j/3][board[i][j]-'0'-1]=true;
		}
		}
		}
		
		this.board=board;
		
		dfs(0,0);
    }

第三步:深度优先+递归,进行填数

递归结束条件:solve(是否已经得出答案)结果为ture,即所有空格都填完了

if(i>=ROW||j>=COL) {
solve=true;
 return;
}

无法填数条件: 数组当前值不是点(空格) 那么当前列到末尾了,向下一行进入,并且重新dfs,如果不是末尾,跳过当前值继续判断下一个值

if(board[i][j]!='.') {
if(j==COL-1) {
dfs(i+1,0);
}else {
dfs(i,j+1);
}
return;
}

开始尝试插值:只有没出现过的才能填入,且填入后立即更新已填的布尔数组,事后变回原样 如果当前行列方格有这个值,跳过,否则尝试添加,并且修改相关行列方格的值。继续判断下一个

for(int number=1;number<=9;number++) {
	//它是不合法的就跳过
	if(row[i][number-1]||col[j][number-1]||mid[i/3*3+j/3][number-1]) {
	continue;
	//否则尝试填入
	}else {
	//尝试填入
	board[i][j]=(char)(number+48);
	//更新已填布尔数组
	row[i][number-1]=true;
	col[j][number-1]=true;
	mid[i/3*3+j/3][number-1]=true;
				
	//向下一个元素出发
	//行末处,跳到下一行
	if(j==COL-1) {
	dfs(i+1,0);
	//未到行末,跳到此行下一个元素
	}else {
	dfs(i,j+1);
	}
				
	//尝试结束,若没有出结果则重置原样
	if(solve) {
		return;
	}else {
	board[i][j]='.';
	row[i][number-1]=false;
	col[j][number-1]=false;
	mid[i/3*3+j/3][number-1]=false;
}}
}


在填写数组这部分代码和前面判断有效非常相似