Solving tips
- Recognize 27 simultaneous 'no duplicates in a group' constraints (9 rows, 9 columns, 9 boxes); each is an O(1) hash-set membership check.
- Visit each cell once, skip '.', and fail the moment a digit is already present in its row, column, or box set; O(n^2) over the board (constant for 9x9).
- The box index flattens to (r // 3) * 3 + c // 3; a bitmask int per group can replace each set to minimize memory.
- Pitfall: don't forget the box check (row/col-only misses box violations), skip empty cells, and get the box formula's coordinate multiplication right.
Problem
Youβre given a 9Γ9 Sudoku board where each cell holds a digit character "1"β"9" or "." for empty. Decide whether the boardβs current filled cells are consistent with the rules:
- No digit repeats within a row.
- No digit repeats within a column.
- No digit repeats within any of the nine 3Γ3 sub-boxes.
Only validate whatβs filled in β the board does not need to be solvable or complete; empty cells are ignored.
Examples
Example 1:
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 β no row, column, or box has a repeated digit.
Example 2: the same board with the top-left 5 changed to 8 β False
Column 0 now holds two 8s (rows 0 and 3), and the top-left 3Γ3 box also gets two 8s.
Example 3: a board whose row 4 is 1 . . . 1 . . . . (all else empty) β False β duplicate 1 in one row.
Constraints
- The board is always 9Γ9; cells are
"1"β"9" or ".".
The input size is fixed, so everything is technically O(1) β the exercise is doing it in one pass with clean bookkeeping instead of 27 separate scans.
Think about it first
Hint 1
Checking one row for duplicates is easy. How many independent "no duplicates" groups does the whole board have?
Hint 2
Every cell belongs to exactly one row, one column, and one box. Could you visit each cell once and record its digit in all three of its groups, failing fast on a repeat?
Hint 3
Keep 9 sets for rows, 9 for columns, 9 for boxes. A cell (r, c) lands in box `(r // 3) * 3 + c // 3`. If the digit is already in any of the three sets β invalid; otherwise add it to all three.
TL;DR
Single pass with hash sets per row/column/box β O(nΒ²) time, O(nΒ²) space for an nΓn board (n = 9, so constant in practice).
Approach 1 β Brute force (re-scan each unit per cell)
The naive intuition: for every filled cell, scan its entire row, its entire column, and its entire 3Γ3 box looking for another cell with the same digit.
from typing import List
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
for r in range(9):
for c in range(9):
d = board[r][c]
if d == ".":
continue
for j in range(9): # row scan
if j != c and board[r][j] == d:
return False
for i in range(9): # column scan
if i != r and board[i][c] == d:
return False
br, bc = 3 * (r // 3), 3 * (c // 3) # box scan
for i in range(br, br + 3):
for j in range(bc, bc + 3):
if (i, j) != (r, c) and board[i][j] == d:
return False
return True
Complexity: O(nΒ³) for an nΓn board (each of the nΒ² cells rescans O(n) cells), O(1) space.
At n = 9 this is only ~2,000 comparisons β nothing βkillsβ it at this fixed size; what it costs you is elegance and scalability, and it re-reads every unit 9 times instead of once.
Approach 2 β One pass with hash sets
The insight: each cell belongs to exactly one row, one column, and one box, and βno duplicates in a groupβ is exactly what a hash set checks in O(1). Keep 27 sets (9 rows, 9 columns, 9 boxes), visit each cell once, and fail the moment a digit re-enters any of its three groups. The box index flattens to (r // 3) * 3 + c // 3.
from typing import List
from collections import defaultdict
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows: defaultdict[int, set[str]] = defaultdict(set)
cols: defaultdict[int, set[str]] = defaultdict(set)
boxes: defaultdict[int, set[str]] = defaultdict(set)
for r in range(9):
for c in range(9):
d = board[r][c]
if d == ".":
continue
b = (r // 3) * 3 + c // 3
if d in rows[r] or d in cols[c] or d in boxes[b]:
return False
rows[r].add(d)
cols[c].add(d)
boxes[b].add(d)
return True
Walkthrough on Example 2 (Example 1βs board with the top-left 5 replaced by 8):
- (0,0) = β8β: box 0. All sets empty β add.
rows[0] = {8}, cols[0] = {8}, boxes[0] = {8}.
- (0,1) = β3β: new everywhere β add. (0,4) = β7β, (1,0) = β6β, (1,3) = β1β, β¦ each first occurrence in its row/col/box β added.
- (2,1) = β9β:
rows[2], cols[1], boxes[0] donβt contain β9β β add (boxes[0] is now {8, 6, 9} after row 1βs β6ββ¦ plus β8β from step 1).
- (3,0) = β8β: row 3 fresh, but
cols[0] already contains β8β (from step 1) β return False. β
(The box check would not have fired here β (3,0) is in box 3 β which is why all three group checks are needed.)
Complexity: O(nΒ²) time β each of the nΒ² cells does O(1) set work; O(nΒ²) space across the 27 sets. For the fixed 9Γ9 board: 81 steps.
Approach 3 β Bitmasks instead of sets
The insight: each group only tracks membership of digits 1β9 β nine booleans β so an int used as a bitmask (bit d set β digit d seen) replaces each hash set, with & as the membership test and | as insert. Same algorithm, denser bookkeeping; this is the version to mention when asked about minimizing constant factors/memory.
from typing import List
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [0] * 9
cols = [0] * 9
boxes = [0] * 9
for r in range(9):
for c in range(9):
ch = board[r][c]
if ch == ".":
continue
bit = 1 << (int(ch) - 1)
b = (r // 3) * 3 + c // 3
if (rows[r] | cols[c] | boxes[b]) & bit:
return False
rows[r] |= bit
cols[c] |= bit
boxes[b] |= bit
return True
Walkthrough on Example 3 (row 4 = 1 . . . 1 . . . ., all else empty):
- (4,0) = β1β: bit =
0b1. rows[4] = 0 β no hit; set rows[4] = 0b1, cols[0] = 0b1, boxes[3] = 0b1.
- (4,4) = β1β: bit =
0b1. rows[4] & 0b1 is nonzero β return False. β
Complexity: O(nΒ²) time, O(n) space (27 machine ints).
Common pitfalls
- Wrong box index: itβs
(r // 3) * 3 + c // 3 β mixing up which coordinate is multiplied by 3 maps cells to the wrong box and lets diagonal duplicates slip through.
- Forgetting to skip
"." β empties would register as βduplicatesβ instantly.
- Validating only rows and columns; box violations (Example 2βs two 8s meet in a box on other boards) are the ones brute-row/col checks miss.
- Testing solvability or completeness β the problem asks only whether the placed digits conflict.
Pattern takeaway
When an object must satisfy several βno duplicates within a groupβ constraints at once, give each group its own hash set (or bitmask) and stream the elements through all of their groups in a single pass β membership checks make each constraint O(1). The only real design work is a clean formula mapping an element to its group ids, like (r // 3) * 3 + c // 3 here.