leetcode36. 有效的数独

法一:自己写的HashSet,惊天四循环
您的代码通过三个独立的循环分别检查行、列和3×3宫格的有效性,逻辑清晰且易于理解。但主要问题在于:
- ••效率较低:进行了三次遍历(行、列、宫格),时间复杂度为O(3×81)=O(243),而最优解可一次遍历完成
- ••冗余操作:每个循环都创建新的HashSet,HashSet的插入和查询操作虽有O(1)平均复杂度,但相比数组直接访问仍有开销
- ••空间使用:HashSet比布尔数组占用更多内存
class Solution {
public boolean isValidSudoku(char[][] board) {
for(int i = 0;i < 9;++i){
HashSet<Character> cSet = new HashSet<>();
for(int j = 0;j < 9;++j){
if(board[i][j] != '.'){
if(cSet.contains(board[i][j])) return false;
else cSet.add(board[i][j]);
}
}
}
for(int i = 0;i < 9;++i){
HashSet<Character> cSet = new HashSet<>();
for(int j = 0;j < 9;++j){
if(board[j][i] != '.'){
if(cSet.contains(board[j][i])) return false;
else cSet.add(board[j][i]);
}
}
}
for(int i = 0;i < 9;i += 3){
for(int j = 0;j < 9;j += 3){
HashSet<Character> cSet = new HashSet<>();
for(int m = i;m < i + 3;++m){
for(int n = j;n < j + 3;++n){
if(board[m][n] != '.'){
if(cSet.contains(board[m][n])) return false;
else cSet.add(board[m][n]);
}
}
}
}
}
return true;
}
}
法二:一次循环,三个 9 * 9的数组分别代表行、列、大九宫的情况。
class Solution {
public boolean isValidSudoku(char[][] board) {
boolean[][] rows = new boolean[9][9];// 初始化三个布尔数组,分别记录行、列、宫格的数字出现情况
boolean[][] cols = new boolean[9][9];
boolean[][] boxes = new boolean[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char ch = board[i][j];
if (ch == '.') continue; // 跳过空格
int num = ch - '1'; // 将字符'1'-'9'转换为索引0-8
int boxIndex = (i / 3) * 3 + j / 3; // 计算宫格索引 // i/3计算的是大九宫格所在行,从0开始
// 检查当前数字是否在行、列、宫格中已存在
if (rows[i][num] || cols[j][num] || boxes[boxIndex][num]) return false;
rows[i][num] = true;// 标记为已出现
cols[j][num] = true;
boxes[boxIndex][num] = true;
}
}
return true;
}
}
法三:位掩码,更极致的性能
class Solution {
public boolean isValidSudoku(char[][] board) {
// 初始化三个长度为9的整数数组,分别用于记录9行、9列、9个3x3宫格中数字的出现情况
// 每个整数有32位,这里利用其中的低9位(第0位到第8位)作为标志位(bit flag)
// 第0位代表数字1,第1位代表数字2,...,第8位代表数字9
// 如果某一位的值为1,表示对应的数字已经在该行/列/宫格中出现过[1,4](@ref)
int[] row = new int[9]; // 行标志位数组
int[] col = new int[9]; // 列标志位数组
int[] box = new int[9]; // 宫格标志位数组
// 遍历数独棋盘上的每一个单元格(共81个)
for (int i = 0; i < 9; i++) { // i 表示当前行号(0-8)
for (int j = 0; j < 9; j++) { // j 表示当前列号(0-8)
// 1. 跳过空格:如果当前单元格是空白(用'.'表示),则不做任何处理,继续检查下一个单元格
if (board[i][j] == '.') continue;
// 2. 计算数字对应的位掩码(bitmask)
// board[i][j] - '1':将字符数字(如'5')转换为对应的整数值(如4)
// 1 << (board[i][j] - '1'):将数字1左移相应的位数,生成一个只有一位是1的二进制数
// 例如,数字'5' 对应的掩码是 1<<4,即二进制 00000000 00000000 00000000 00010000(十进制16)
// 这个掩码就像一把独特的“钥匙”,用来检查和更新标志位[1,4](@ref)
int mask = 1 << (board[i][j] - '1');
// 3. 计算当前单元格属于哪个3x3宫格(编号0-8)
// 数独盘被分为9个3x3宫格,计算规则是:宫格编号 = (行号/3)*3 + 列号/3
// 例如,单元格(4,5):i=4, j=5 -> (4/3)=1, 1 * 3=3, j/3=1 -> 3+1=4,属于第4号宫格(正中间)[1,2](@ref)
int boxIndex = (i / 3) * 3 + j / 3;
// 4. 冲突检测:使用按位与操作(&)检查当前数字是否在第i行、第j列或第boxIndex个宫格中已经出现过
// 原理:按位与(&)操作会比较两个数的二进制位,只有当对应位都为1时,结果的对应位才为1
// - (row[i] & mask) > 0:检查row[i]中代表当前数字的那一位是否为1(即是否已在当前行出现过)
// - 对列(col[j])和宫格(box[boxIndex])进行同样的检查
// 如果任意一个条件为真(结果>0),说明数字重复,数独无效,立即返回false[1,4](@ref)
if ((row[i] & mask) > 0 || (col[j] & mask) > 0 || (box[boxIndex] & mask) > 0) {
return false;
}
// 5. 更新标志位:如果没有冲突,使用按位或操作(|)将当前数字的掩码“记录”到对应的行、列、宫格标志位中
// 原理:按位或(|)操作会将两个数的二进制位进行合并,只要某个位是1,结果对应位就是1
// 例如,row[i] 原本是 0(二进制全0),与 mask(如00010000)进行按位或操作后,row[i] 变成了 00010000
// 这表示在第i行,数字5(对应第4位)已经被标记为“已出现”[1,4](@ref)
row[i] |= mask; // 更新行的数字出现记录
col[j] |= mask; // 更新列的数字出现记录
box[boxIndex] |= mask; // 更新宫格的数字出现记录
}
}
// 如果成功遍历完所有单元格,都没有发现重复数字,说明该数独盘面是有效的,返回true
return true;
}
}
浙公网安备 33010602011771号