LEETCODE算法注解36:
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。
解法说明:在我看来,这道题不论是处理行,还是列,或者九宫格都不是问题,难点在于如何巧妙的判定1-9的不重复出现。
参考:https://leetcode-cn.com/problems/valid-sudoku/solution/java-wei-yun-suan-xiang-jie-miao-dong-zuo-biao-bia/
给出一种规范解法。
难点1:如何表示遍历一个九宫格呢?
上图不同颜色代表不同的九宫格。
我们知道某个位置的坐标是第几个宫格第几个位置,那如何知道它是哪一个坐标呢。
A是5宫格3位置。
一行有三个九宫格,(5/3)*3表示5上面有3小行,一个小的九宫格每行有3小格,3/3代表在第5个九宫格中3位置上面还有1小行,1+3=4,说明A是第4行。
求列同理:(5%3)*3+3%3=6
给出规律:当前为第i九宫格第j个位置
则行列为board[3∗(i/3)+j/3] [3∗(i%3)+j%3]
难点二:如何表示9个数字均不相同?这里用位运算来解决。
本题可以使用一个10位二进制数判断数字是否被访问。第k位数为1代表已加入,为0代表未加入(从第0为开始,虽然此0位并无作用)。
初始时val=0,代表没有数加入。假设现在传入数字为3,我们将val右移三位,与1相与。
val右移三位还是 0 000 000 000 与0 000 000 001 相与,相与为0代表新加入,为1代表重复。这里很明显是加入数字。将1左移三位0 000 001 000与val异或为0 000 001 000 存为新的val。
再举一例:
val = 0 100 010 100 现在传入n=2,
val右移2为得到 val = 0 001 000 101与 0 000 000 001相与得到 0 000 000 001,所以表示重复。很明显看到原本val第二位是1,所以运算正确。
做法总结:
更新方式(记九位数为val,传入的数字为n):
判断是否加入:将十位数右移位n位,与1进行与运算
结果为0:未加入,将传入的数字加入十位数
结果为1:已加入,返回false
将传入的数字加入十位数:将1左移位n位,与val异或即可
代码:
public boolean isValidSudoku(char[][] board) {
for(int i = 0; i < 9; i ++){
// hori, veti, sqre分别表示行、列、小宫
int hori = 0, veti = 0, sqre = 0;
for(int j = 0; j < 9; j ++){
// 由于传入为char,需要转换为int,减48 ascii '0'=48
int h = board[i][j] - 48;//便利第i行
int v = board[j][i] - 48;//遍历第i列
int s = board[3 * (i / 3) + j / 3][3 * (i % 3) + j % 3] - 48; //遍历第i个九宫格
// "."的ASCII码为46,故小于0代表着当前符号位".",不用讨论
if(h > 0){
hori = sodokuer(h, hori);
}
if(v > 0){
veti = sodokuer(v, veti);
}
if(s > 0){
sqre = sodokuer(s, sqre);
}
if(hori == -1 || veti == -1 || sqre == -1){
return false;
}
}
}
return true;
}
//传入的值重复了,返回-1,正确传入返回更新的val
private int sodokuer(int n, int val){
return ((val >> n) & 1) == 1 ? -1 : val ^ (1 << n);
}
另外参考https://leetcode-cn.com/u/angus-liu/
有另外一种巧妙解法,已做详细注释,不再做解释
class Solution {
public boolean isValidSudoku(char[][] board) {
// 记录某行,某位数字是否已经被摆放
boolean[][] row = new boolean[9][9];
// 记录某列,某位数字是否已经被摆放
boolean[][] col = new boolean[9][9];
// 记录某 3x3 宫格内,某位数字是否已经被摆放
boolean[][] block = new boolean[9][9];
//这解法真牛
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
if (board[i][j] != '.')
{
//将char的1-9处理成int类型且数组不会溢出的0-8
//说明:其实九宫格中是哪九个数字并无差别,只要不相同即可,因为这里是存在数组中,0-8刚好符合数组的范围
int num = board[i][j] - '1';
//判断是哪个九宫格
int blockIndex = i / 3 * 3 + j / 3;
//当某行某列或某九宫格中出现重复数字是,if则判定成功,返回false
if (row[i][num] || col[j][num] || block[blockIndex][num])
{
return false;
}
else
{
//行,列,九宫格,中未曾出现过赋值为true
row[i][num] = true;
col[j][num] = true;
block[blockIndex][num] = true;
}
}
}
}
return true;
}
}
来源:CSDN
作者:qq_39502383
链接:https://blog.csdn.net/qq_39502383/article/details/104107481