36. Valid Sudoku - Explanation
Description
You are given a 9 x 9 Sudoku board board. A Sudoku board is valid if the following rules are followed:
- Each row must contain the digits
1-9without duplicates. - Each column must contain the digits
1-9without duplicates. - Each of the nine
3 x 3sub-boxes of the grid must contain the digits1-9without duplicates.
Return true if the Sudoku board is valid, otherwise return false
Note: A board does not need to be full or be solvable to be valid.
Example 1:
Input: board =
[["1","2",".",".","3",".",".",".","."],
["4",".",".","5",".",".",".",".","."],
[".","9","8",".",".",".",".",".","3"],
["5",".",".",".","6",".",".",".","4"],
[".",".",".","8",".","3",".",".","5"],
["7",".",".",".","2",".",".",".","6"],
[".",".",".",".",".",".","2",".","."],
[".",".",".","4","1","9",".",".","8"],
[".",".",".",".","8",".",".","7","9"]]
Output: trueExample 2:
Input: board =
[["1","2",".",".","3",".",".",".","."],
["4",".",".","5",".",".",".",".","."],
[".","9","1",".",".",".",".",".","3"],
["5",".",".",".","6",".",".",".","4"],
[".",".",".","8",".","3",".",".","5"],
["7",".",".",".","2",".",".",".","6"],
[".",".",".",".",".",".","2",".","."],
[".",".",".","4","1","9",".",".","8"],
[".",".",".",".","8",".",".","7","9"]]
Output: falseExplanation: There are two 1's in the top-left 3x3 sub-box.
Constraints:
board.length == 9board[i].length == 9board[i][j]is a digit1-9or'.'.
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(n^2) time and O(n^2) space, where n is the number of rows in the square grid.
Hint 1
Which data structure would you prefer to use for checking duplicates?
Hint 2
You can use a hash set for every row and column to check duplicates. But how can you efficiently check for the squares?
Hint 3
We can find the index of each square by the equation (row / 3) * 3 + (col / 3). Then we use hash set for O(1) lookups while inserting the number into its row, column and square it belongs to. We use separate hash maps for rows, columns, and squares.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Sets - Used to track seen digits in rows, columns, and boxes for duplicate detection
- 2D Array Traversal - Understanding how to iterate through a 9x9 grid and compute box indices
- Bit Manipulation - The optimized solution uses bitmasks to represent seen digits compactly
1. Brute Force
Intuition
A valid Sudoku board must follow three rules:
- Each row can contain digits
1–9at most once. - Each column can contain digits
1–9at most once. - Each 3×3 box can contain digits
1–9at most once.
We can directly check all these conditions one by one.
For every row, every column, and every 3×3 box, we keep a set of seen digits and make sure no number appears twice.
If we ever find a duplicate in any of these three checks, the board is invalid.
Algorithm
Check all rows:
- For each row index
rowfrom0to8:- Create an empty set
seen. - For each column index
ifrom0to8:- Skip if the cell is
".". - If the value is already in
seen, returnfalse. - Otherwise, add it to
seen.
- Skip if the cell is
- Create an empty set
- For each row index
Check all columns:
- For each column index
colfrom0to8:- Create an empty set
seen. - For each row index
ifrom0to8:- Skip if the cell is
".". - If the value is already in
seen, returnfalse. - Otherwise, add it to
seen.
- Skip if the cell is
- Create an empty set
- For each column index
Check all 3×3 boxes:
- Number the 3×3 boxes from
0to8. - For each
square:- Create an empty set
seen. - For
iin0..2andjin0..2:- Compute:
row = (square // 3) * 3 + icol = (square % 3) * 3 + j
- Skip if the cell is
".". - If the value is already in
seen, returnfalse. - Otherwise, add it to
seen.
- Compute:
- Create an empty set
- Number the 3×3 boxes from
If all rows, columns, and 3×3 boxes pass these checks without duplicates, return
true.
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
for row in range(9):
seen = set()
for i in range(9):
if board[row][i] == ".":
continue
if board[row][i] in seen:
return False
seen.add(board[row][i])
for col in range(9):
seen = set()
for i in range(9):
if board[i][col] == ".":
continue
if board[i][col] in seen:
return False
seen.add(board[i][col])
for square in range(9):
seen = set()
for i in range(3):
for j in range(3):
row = (square//3) * 3 + i
col = (square % 3) * 3 + j
if board[row][col] == ".":
continue
if board[row][col] in seen:
return False
seen.add(board[row][col])
return TrueTime & Space Complexity
- Time complexity:
- Space complexity:
2. Hash Set (One Pass)
Intuition
Instead of checking rows, columns, and 3×3 boxes separately, we can validate the entire Sudoku board in one single pass.
For each cell, we check whether the digit has already appeared in:
- the same row
- the same column
- the same 3×3 box
We track these using three hash sets:
rows[r]keeps digits seen in rowrcols[c]keeps digits seen in columncsquares[(r // 3, c // 3)]keeps digits in the 3×3 box
If a digit appears again in any of these places, the board is invalid.
Algorithm
Create three hash maps of sets:
rowsto track digits in each rowcolsto track digits in each columnsquaresto track digits in each 3×3 sub-box, keyed by(r // 3, c // 3)
Loop through every cell in the board:
- Skip the cell if it contains
".". - Let
valbe the digit in the cell. - If
valis already in:rows[r]→ duplicate in the rowcols[c]→ duplicate in the columnsquares[(r // 3, c // 3)]→ duplicate in the 3×3 box
Then returnfalse.
- Skip the cell if it contains
Otherwise, add the digit to all three sets:
rows[r]cols[c]squares[(r // 3, c // 3)]
If the whole board is scanned without detecting duplicates, return
true.
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
cols = defaultdict(set)
rows = defaultdict(set)
squares = defaultdict(set)
for r in range(9):
for c in range(9):
if board[r][c] == ".":
continue
if ( board[r][c] in rows[r]
or board[r][c] in cols[c]
or board[r][c] in squares[(r // 3, c // 3)]):
return False
cols[c].add(board[r][c])
rows[r].add(board[r][c])
squares[(r // 3, c // 3)].add(board[r][c])
return TrueTime & Space Complexity
- Time complexity:
- Space complexity:
3. Bitmask
Intuition
Every digit from 1 to 9 can be represented using a single bit in an integer.
For example, digit 1 uses bit 0, digit 2 uses bit 1, …, digit 9 uses bit 8.
This means we can track which digits have appeared in a row, column, or 3×3 box using just one integer per row/column/box instead of a hash set.
When we encounter a digit, we compute its bit position and check:
- if that bit is already set in the row → duplicate in row
- if that bit is already set in the column → duplicate in column
- if that bit is already set in the box → duplicate in box
If none of these checks fail, we “turn on” that bit to mark the digit as seen.
This approach is both memory efficient and fast.
Algorithm
Create three arrays of size 9:
rows[i]stores bits for digits seen in rowicols[i]stores bits for digits seen in columnisquares[i]stores bits for digits seen in 3×3 boxi
Loop through each cell
(r, c)of the board:- Skip if the cell contains
".". - Convert the digit to a bit index:
val = int(board[r][c]) - 1. - Compute the mask:
mask = 1 << val.
- Skip if the cell contains
Check for duplicates:
- If
maskis already set inrows[r], returnfalse. - If
maskis already set incols[c], returnfalse. - If
maskis already set insquares[(r // 3) * 3 + (c // 3)], returnfalse.
- If
Mark the digit as seen:
rows[r] |= maskcols[c] |= masksquares[(r // 3) * 3 + (c // 3)] |= mask
If all cells are processed without conflicts, return
true.
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [0] * 9
cols = [0] * 9
squares = [0] * 9
for r in range(9):
for c in range(9):
if board[r][c] == ".":
continue
val = int(board[r][c]) - 1
if (1 << val) & rows[r]:
return False
if (1 << val) & cols[c]:
return False
if (1 << val) & squares[(r // 3) * 3 + (c // 3)]:
return False
rows[r] |= (1 << val)
cols[c] |= (1 << val)
squares[(r // 3) * 3 + (c // 3)] |= (1 << val)
return TrueTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Wrong Box Index Calculation
The 3x3 box index formula (r // 3) * 3 + (c // 3) is easy to get wrong. A common mistake is using (r // 3, c // 3) as a tuple key but forgetting integer division, or computing r // 3 + c // 3 which doesn't uniquely identify boxes.
# Wrong: doesn't uniquely identify boxes
box_idx = r // 3 + c // 3 # Box (0,1) and (1,0) both give 1
# Correct: unique box index 0-8
box_idx = (r // 3) * 3 + c // 3Not Skipping Empty Cells
Empty cells are represented by "." and should be skipped entirely. Forgetting to check for empty cells before processing will cause errors or incorrect duplicate detection.
Checking Validity vs Solvability
This problem only checks if the current board state is valid, not whether the puzzle is solvable. A board with no duplicates is valid even if it's impossible to complete.
Processing the Same Cell Multiple Times
When iterating through the board, make sure each cell is only processed once. Some implementations accidentally check the same digit multiple times when validating rows, columns, and boxes separately.
Sign in to join the discussion