uacs2024

导航

leetcode36. 有效的数独

36. 有效的数独

微信截图_20251114162904

法一:自己写的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;
    }
}

 

posted on 2025-11-14 16:46  ᶜʸᵃⁿ  阅读(3)  评论(0)    收藏  举报