数独这道题,乍一看像是「规则判断题」,但真正写代码的时候,很多人会卡在一个问题上:
如何在遍历棋盘的同时,快速判断当前数字在“行 / 列 / 九宫格”里是否重复?
这篇笔记我会从:
- 题目要求
- 直觉思路 → 数据结构设计
- 九宫格下标的推导
- 完整代码
- 逐行代码拆解
- 总结
一步一步把这道题讲清楚。
一、题目要求回顾
给定一个 9 × 9 的数独棋盘 board,判断当前状态是否合法。
规则如下:
- 每一行,数字
1 ~ 9不能重复 - 每一列,数字
1 ~ 9不能重复 - 每一个
3 × 3的九宫格中,数字1 ~ 9不能重复 '.'表示空格,不参与判断
注意:
题目只要求判断“当前状态是否合法”,不要求这个数独能被解出来。
二、最核心的问题:怎么“同时”判断三种约束?
如果每遇到一个数字,就:
- 扫一遍当前行
- 再扫一遍当前列
- 再扫一遍九宫格
时间复杂度会变高,而且代码会非常乱。
更合理的思路是:
在遍历棋盘的过程中,用空间换时间,把“是否出现过”记录下来。
三、数据结构设计:三个二维数组
我们分别记录:
row[i][num]:第i行是否出现过数字numcol[j][num]:第j列是否出现过数字numbox[k][num]:第k个九宫格是否出现过数字num
boolean[][] row = new boolean[9][9];
boolean[][] col = new boolean[9][9];
boolean[][] box = new boolean[9][9];
这里的 num 范围是 0 ~ 8,对应数字 '1' ~ '9'。
四、九宫格编号是怎么来的?
这是很多人第一次写这题时最迷惑的地方。
九宫格的分布
0 | 1 | 2
--+---+--
3 | 4 | 5
--+---+--
6 | 7 | 8
推导过程
- 每 3 行是一排宫格 →
i / 3 - 每 3 列是一列宫格 →
j / 3 - 行优先编号 →
行号 * 3 + 列号
所以九宫格编号就是:
int boxIndex = (i / 3) * 3 + j / 3;
这个公式不是记忆出来的,而是结构推导出来的。
五、完整代码
class Solution {
public boolean isValidSudoku(char[][] board) {
boolean[][] row = new boolean[9][9];
boolean[][] col = new boolean[9][9];
boolean[][] box = new boolean[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
// 1. 空格直接跳过
if (c == '.') continue;
// 2. 将字符 '1'~'9' 映射为 0~8
int num = c - '1';
// 3. 计算当前格子所在的九宫格编号
int boxIndex = (i / 3) * 3 + j / 3;
// 4. 如果在行 / 列 / 宫格中已出现,直接返回 false
if (row[i][num] || col[j][num] || box[boxIndex][num]) {
return false;
}
// 5. 标记当前数字已出现
row[i][num] = true;
col[j][num] = true;
box[boxIndex][num] = true;
}
}
return true;
}
}
六、逐行拆解代码在做什么
1.为什么 c == '.' 直接跳过?
空格不代表任何数字,也不参与合法性判断。
如果不跳过,c - '1' 会得到非法下标。
2. int num = c - '1' 的意义
字符 '1' ~ '9' → 映射为数组下标 0 ~ 8
这是把「数独数字」转为「数组索引」的关键一步。
3.三个判断为什么能同时做?
if (row[i][num] || col[j][num] || box[boxIndex][num])
含义非常直白:
- 这个数字在当前行出现过?
- 在当前列出现过?
- 在当前九宫格出现过?
只要有一个为 true,就违反规则。
4.为什么只需要一次遍历?
因为:
- 每个数字只会被访问一次
- 状态一旦被标记,后续再遇到就能 O(1) 判断
- 不需要回头、不需要额外扫描
时间复杂度是 O(81) ,空间换时间,非常稳。
七、这道题的本质
这不是一道“数独题”,而是一道:
多维约束状态记录题
关键能力是:
- 把规则转化为「是否出现过」
- 把二维问题映射到一维编号
- 用布尔数组做快速判重
八、总结
- 行、列、九宫格的约束可以并行判断
- 九宫格编号
(i / 3) * 3 + j / 3是结构推导结果 - 使用
boolean[][]能把判断降到 O(1) - 代码短,但逻辑非常“工程化”