LeetCode 第36题:有效的数独

115 阅读4分钟

LeetCode 第36题:有效的数独

题目描述

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:

  • 一个有效的数独(部分已填)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 空白格用 '.' 表示。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

数独示例1

输入: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

示例 2:

输入:board = 
[["8","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"]]
输出:false
解释:第一行的第一个数字 8 在第四行中重复出现

提示

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一个数字 1-9 或者 '.'

解题思路

方法一:哈希表记录

使用三个不同的哈希表分别记录每行、每列、每个3x3方格中数字的出现情况。

关键点:

  1. 使用哈希集合记录每个区域的数字
  2. 巧妙地计算3x3方格的索引
  3. 一次遍历完成所有验证

具体步骤:

  1. 创建三个哈希表数组:
    • rows[9]: 记录每行的数字
    • cols[9]: 记录每列的数字
    • boxes[9]: 记录每个3x3方格的数字
  2. 遍历整个数独板:
    • 计算当前位置属于哪个3x3方格
    • 检查数字在三个区域是否重复
    • 如果重复返回false

时间复杂度:O(1),因为是固定大小的9x9网格 空间复杂度:O(1),使用固定大小的哈希表

方法二:位运算优化

使用位运算来优化空间使用,每个数字用一个位表示。

图解思路

验证过程分析表

步骤验证区域检查内容结果说明
初始状态第一行数字5有效第一次出现数字5
继续检查第一列数字5有效列中第一次出现5
继续检查第一个3x3宫数字5有效3x3宫中第一次出现5
发现重复第四行数字8无效与第一行的8重复

数据结构状态表

数据结构用途存储内容优势
HashSet数组行验证每行出现的数字查找O(1)
HashSet数组列验证每列出现的数字空间友好
HashSet数组3x3宫验证每个宫内数字实现简单
位运算数组全部验证二进制位表示性能最优

代码实现

哈希表版本

public class Solution {
    public bool IsValidSudoku(char[][] board) {
        var rows = new HashSet<char>[9];
        var cols = new HashSet<char>[9];
        var boxes = new HashSet<char>[9];
      
        for (int i = 0; i < 9; i++) {
            rows[i] = new HashSet<char>();
            cols[i] = new HashSet<char>();
            boxes[i] = new HashSet<char>();
        }
      
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    char num = board[i][j];
                    int boxIndex = (i / 3) * 3 + j / 3;
                  
                    if (rows[i].Contains(num) || 
                        cols[j].Contains(num) || 
                        boxes[boxIndex].Contains(num)) {
                        return false;
                    }
                  
                    rows[i].Add(num);
                    cols[j].Add(num);
                    boxes[boxIndex].Add(num);
                }
            }
        }
      
        return true;
    }
}

位运算版本

public class Solution {
    public bool IsValidSudoku(char[][] board) {
        int[] rows = new int[9];
        int[] cols = new int[9];
        int[] boxes = new int[9];
      
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '1';
                    int boxIndex = (i / 3) * 3 + j / 3;
                    int bit = 1 << num;
                  
                    if ((rows[i] & bit) > 0 || 
                        (cols[j] & bit) > 0 || 
                        (boxes[boxIndex] & bit) > 0) {
                        return false;
                    }
                  
                    rows[i] |= bit;
                    cols[j] |= bit;
                    boxes[boxIndex] |= bit;
                }
            }
        }
      
        return true;
    }
}

执行结果

哈希表版本:

  • 执行用时:88 ms
  • 内存消耗:43.2 MB

位运算版本:

  • 执行用时:80 ms
  • 内存消耗:42.8 MB

代码亮点

  1. 🎯 巧妙的3x3方格索引计算:boxIndex = (i / 3) * 3 + j / 3
  2. 💡 使用位运算优化空间和性能
  3. 🔍 一次遍历完成所有验证
  4. 🎨 代码结构清晰,易于理解和维护

常见错误分析

  1. 🚫 3x3宫格索引计算错误
  2. 🚫 没有跳过空白格子('.')
  3. 🚫 重复检查同一个位置
  4. 🚫 位运算实现时位移计算错误

解法对比

解法时间复杂度空间复杂度优点缺点
暴力验证O(1)O(1)直观简单代码冗长
哈希表法O(1)O(1)易于理解空间较大
位运算法O(1)O(1)性能最优可读性差

相关题目