请你判断一个 9 x 9
的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
注意:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 空白格用
'.'
表示。
示例 :
输入: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"]] 输出:true
位运算
由于数独是9x9的方格,这里总共需要判断9行、9列、9个3x3方格中的数是否有重复。
创建三个位掩码数组:row
、col
和 block
,每个数组有 9 个元素,用于跟踪每个行、列和块中出现的数字。
以例子中第一行为例,初始状态row[0] = 0,二进制表示为0b0000000000。
当遍历board[i,j]时,需要对row[i],col[j],block[i/3*3 + j/3]进行更新。
注意这里的block[i/3*3 + j/3],为什么是下标是i/3*3 + j/3?
首先注意这里的除法都是整除向下取整,因此下标0,1,2经过除法后为0,下标3,4,5经过除法后为1,按照上图对3x3网格进行分块:
- [012,012] -> 块0
- [012,345] -> 块1
- [012,678] -> 块2
- [345,012] -> 块3
- ......
回到例子中,第一步遍历board[0][0] = 5,那么代码执行row[0] = 0b0000000000 | (1 << 5),即0b0000010000。
对81个方格重复这个位运算的过程。
在这个遍历的过程中进行判断,如果计算之前这位就是1了,说明在这一行/列/块中已经出现过对应数字,直接返回无效。
class Solution {
public boolean isValidSudoku(char[][] board) {
int[] row = new int[9];
int[] col = new int[9];
int[] block = new int[9];
for(int i = 0;i < 9;i++){
for(int j = 0;j < 9;j++){
if(board[i][j] == '.'){
continue;
}
int num = board[i][j] - '0';
int block_num = i / 3 * 3 + j / 3;
if(((row[i] >> num) & 1) == 1 || ((col[j] >> num) & 1) == 1|| ((block[block_num] >> num) & 1) == 1){
return false;
}
row[i] = row[i] | (1 << num);
col[j] = col[j] | (1 << num);
block[block_num] = block[block_num] | (1 << num);
}
}
return true;
}
}
算法概述:
- 创建三个位掩码数组:
row
、col
和block
,每个数组有 9 个元素,用于跟踪每个行、列和块中出现的数字。 - 遍历棋盘,对于每个非空单元格:
- 计算单元格所在的块号
block_num
。 - 如果数字在当前行、列或块中已经出现(即位掩码数组中相应位为 1),则返回 false。
- 否则,将数字标记为在当前行、列和块中出现,即在相应位掩码数组中设置相应位为 1。
- 计算单元格所在的块号
- 如果遍历完棋盘,返回 true。
除去位运算方式,还可以通过二维数组,哈希表对遍历结果进行存储,本质上都是遍历一次网格,记录是否出现重复元素,只有存储方式上的差别。