1.题目
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 '.' 表示。
2.示例
提示:
board.length == 9
board[i].length == 9
board[i][j]
是一位数字(1-9
)或者'.'
3.分析:
此题如果想明白了,就非常简单,否则的话,只能用暴力算法破解,时间复杂度至少是O(n^2)。这里不介绍暴力破解算法,介绍一种比较常用的方法。
判断是否满足题中要求的数独,必须满足:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
因此在判断的时候,可以分别判断行,列和每个3*3宫格。但是无论怎么判断都有一个规律,那就是每行、每列和每个3*3宫格最多只有9个数,而且大小都是1-9。
根据这个规律,我们可以创建一个数组temp[10]={0},用来把每行或没列或每个宫格的数字存在这个数组的对应位置上,例如:
假设某行的数字是:
["5","3",".",".","7",".",".",".","."]
第一个数字是5,那我们可以把这个数字保存在temp[5]=5;
第二个数字是3,那我们可以把这个数字保存在temp[3]=3;
第五个数字是7,那我们可以把这个数字保持在temp[7]=7;
那这样保存后,有什么用啦?那这里再举一个例子:
假设某行的数字是:
["5","3",".",".","7",".","5",".","."]
第一个数字是5,那我们可以把这个数字保存在temp[5]=5;
第二个数字是3,那我们可以把这个数字保存在temp[3]=3;
第五个数字是7,那我们可以把这个数字保持在temp[7]=7;
第七个数字是5,这个时候若还要把数字保存到temp数组中时,就会保存到temp[5]中,但是temp[5]中已经有数字5了,这个时候就可以判断这行数字不符合数独的特点,进而这个这个9*9数独是无效的。
同理,判断每列和每个3*3数独时是同样的道理。
4.代码:
bool isValidSudoku(char** board, int boardSize, int* boardColSize) {
int temp[10] = { 0 };
int just = 0;
//判断每行,每轮循环都只判断一行数字
for (int i = 0; i < 9; i++) //这个for代表行数
{
just = 0;//一个标志位,为0表示本行数字满足数独的特点,为1表示本行数字不符合数独的特点,因此就退出循环,直接表示9*9数独无效。
for (int t = 0; t < 10; t++)temp[t] = 0;//每轮必须保证temp数组元素最开始都是1-9之外的数字,这里直接赋值为0
for (int j = 0; j < 9; j++) //这个for表示这一行的所有元素
{
if (board[i][j] == '.')continue;
if (temp[(board[i][j] - '0')] != 0) //发现temp数组对应下标的元素不为0了,表示先前已经被赋值,表示本行一定出现了2个相同的数字,此时9*9数独无效
{
just = 1;//标志位为0
break;//跳出内层循环
}
//如果不为0,直接给temp数组的对应下标元素赋值——temp[元素值]=元素值
temp[(board[i][j] - '0')] = (board[i][j] - '0');
}
if (just == 1)break;//判断内层循环结束后,也就是本轮循环检查结束后,该行是否满足数独的条件。如果为1,就不满足,直接跳出外层循环,不需要再检查其他行了,反之继续检查其他行。
}
if (just == 1)return false;//判断每行是否满足数独的条件。如果just==1,表示必有一行不满足数独的条件,直接返回false;反之进入每列的检查。
//判断每列,原理与每行的检查完全一行,这里不赘述了。
just = 0;
for (int j = 0; j < 9; j++)
{
just = 0;
for (int t = 0; t < 10; t++)temp[t] = 0;
for (int i = 0; i < 9; i++)
{
if (board[i][j] == '.')continue;
if (temp[(board[i][j] - '0')] != 0)
{
just = 1;
break;
}
temp[(board[i][j] - '0')] = (board[i][j] - '0');
}
if (just == 1)break;
}
if (just == 1)return false;//判断每列是否满足数独的条件。如果just==1,表示必有一行不满足数独的条件,直接返回false;反之进入每列的检查。
//判断每个3*3宫内,这里的代码怎么写,有多种方式,并不唯一。
just = 0;
for (int i = 0; i < 3; i++)//这个for依旧代表行,表示每行有3个3*3数独
{
just = 0;//还是标志位,作用一样
for (int j = 0; j < 3; j++)//这个for代表每列,表示每列有3个3*3数独
{
just = 0;//还是标志位,作用一样
for (int t = 0; t < 10; t++)temp[t] = 0;//保证每次检查一个3*3数独时,temp数组的所有元素都要是0
//这里是每个3*3数独的检查
for (int m = i * 3; m <= i * 3 + 2; m++)//每个3*3数独的每行
{
for (int n = j * 3; n <= j * 3 + 2; n++)//每个3 * 3数独的每列
{
if (board[m][n] == '.')continue;
if (temp[(board[m][n] - '0')] != 0)//与行检查时是一样的原理,不赘述
{
just = 1;
break;
}
temp[(board[m][n] - '0')] = (board[m][n] - '0');
}
if (just == 1)break;
}
if (just == 1)break;
}
if (just == 1)break;
}
if (just == 1)return false;//判断每个3*3是否是满足数独的。若just=1,表示至少一个不满足,所以返回false
return true;//能走到这里,表示这个9*9数独是有效的
}
5.总结
本题就在于思路的转换。最普通的想法就是在检查每行,每列和每个3*3时进行轮询。这样做的时间复杂度是非常大的,即使只检查一行,也要循环81次。但如果采用上面的思想,把所有行检查完,也才循环81次。
综上,本题并不复杂,多做题后,自然就会有这种思路。