#36 哈希表 中等

有效的数独

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3×3 宫内只能出现一次。

注意:一个有效的数独(部分已被填充)不一定是可解的;只需验证已填数字是否违反规则;空白格用 '.' 表示。

示例 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
左上角 3×3 宫内有两个 8,违反宫格规则。

💭 模拟答题者思考

1. 我先想暴力:对每个已填数字,分别检查它所在行、列、3×3 宫有没有重复——每格要扫最多 9 个邻居,整体约 O(81×9),虽能过但重复劳动多。

2. 重复在哪里?每往右/往下扫一格,其实只是在问:「这个数字在我已经看过的同行/同列/同宫里出现过吗?」——本质是集合查重,不必每次重新遍历整行整列。

3. 关键转化:开 9 个行集合、9 个列集合、9 个宫集合;扫到 (i,j) 的数字 c 时,算宫号 b=(i//3)*3+j//3,若 c 已在 rows[i]/cols[j]/boxes[b] 任一集合中则立即 false,否则三处都加入 c

4. 例 2 左上角宫:先记入 8,3,再扫到第二个 8boxes[0] 已有 8,直接判无效——不必解完整数独。

5. 棋盘固定 9×9,最多 81 格、每格 O(1) 查集合,总复杂度 O(1);空间也是 27 个小集合的常数级。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
rows[i]set<char> × 9定义:第 i 行已出现过的数字集合
维护:扫描到 (i,j) 时,rows[i] 恰好包含该行 j 左侧及当前格的所有非空数字
更新:遇到数字 c 时,若 c in rows[i] 则非法,否则 rows[i].add(c)
cols[j]set<char> × 9定义:第 j 列已出现过的数字集合
维护:与行对称,保证列内 1-9 不重复
更新:同上,重复则返回 false,否则加入集合
boxes[b]set<char> × 9定义:第 b3×3 宫格已出现过的数字集合,b = (i//3)*3 + j//3
维护:每个宫格独立维护,与行、列约束并行检查
更新:若 c in boxes[b] 则非法,否则 boxes[b].add(c)
c = board[i][j]char定义:当前格字符,'.' 表示空白
维护:仅对 '1'..'9' 执行去重检查,空白格直接跳过
更新:双重循环逐格推进,每遇到一个数字同时查行、列、宫三套集合

⌨️ 落码步骤

1. 初始化 rows, cols, boxes 为 9 个空集合

2. 双重循环遍历每个格子 (i, j)

3. 若 board[i][j] == '.' 跳过;否则令 c = board[i][j]b = (i//3)*3 + j//3

4. 若 c 已在 rows[i]cols[j]boxes[b] 中,返回 false

5. 否则将 c 同时加入三个集合;全部扫完返回 true

💻 代码实现

class Solution:
    def isValidSudoku(self, board: list[list[str]]) -> bool:
        rows = [set() for _ in range(9)]
        cols = [set() for _ in range(9)]
        boxes = [set() for _ in range(9)]
        for i in range(9):
            for j in range(9):
                c = board[i][j]
                if c == '.':
                    continue
                b = (i // 3) * 3 + j // 3
                if c in rows[i] or c in cols[j] or c in boxes[b]:
                    return False
                rows[i].add(c)
                cols[j].add(c)
                boxes[b].add(c)
        return True
class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        vector<unordered_set<char>> rows(9), cols(9), boxes(9);
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                char c = board[i][j];
                if (c == '.') continue;
                int b = (i / 3) * 3 + j / 3;
                if (rows[i].count(c) || cols[j].count(c) || boxes[b].count(c))
                    return false;
                rows[i].insert(c);
                cols[j].insert(c);
                boxes[b].insert(c);
            }
        }
        return true;
    }
};
// 时间 O(1),空间 O(1)(棋盘规模固定)

📈 复杂度分析

时间复杂度 O(1)
空间复杂度 O(1)

⚠️ 常见坑

宫格编号公式写错:应是 (i//3)*3 + j//3,不是 i//3 + j//3(i%3)*3 + j%3 的误用。

本题只验证合法性,不要求可解;看到矛盾直接 false,不要尝试回溯填数。

字符类型是 '1'..'9''.',不要当成 int;空白格必须跳过,否则会把 '.' 当数字处理。

🔍 必测边界 Case

Case 1:全空白盘
board 全是 '.' → true(无冲突可判)
Case 2:行内重复
同一行两个 '5' → false
Case 3:宫内重复(示例 2)
左上角 3×3 宫两个 '8' → false
Case 4:列内重复
同一列两个 '7' → false
Case 5:合法但不可解
已填数字互不冲突即可返回 true,不要求能填满全盘