9. Valid Sudoku
🔗 LeetCode Problem: 36. Valid Sudoku
📊 Difficulty: Medium
🏷️ Topics: Array, Hash Table, Matrix
Problem Statement
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:
- Each row must contain the digits
1-9without repetition. - Each column must contain the digits
1-9without repetition. - Each of the nine
3 x 3sub-boxes of the grid must contain the digits1-9without repetition.
Note: - A Sudoku board (partially filled) could be valid but is not necessarily solvable. - Only the filled cells need to be validated according to the mentioned rules.
Example 1:
Input: 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"]]
Output: true
Example 2:
Input: 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"]]
Output: false
Explanation: Same as Example 1, except with the 5 in the top left corner being modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid.
Constraints:
- board.length == 9
- board[i].length == 9
- board[i][j] is a digit 1-9 or '.'
🎨 Visual Understanding
The Three Rules Visualized
Rule 1: Each ROW must have unique digits (no duplicates)
Row 0: [5, 3, ., ., 7, ., ., ., .]
All filled cells different ✓
Row 1: [6, ., ., 1, 9, 5, ., ., .]
6, 1, 9, 5 all different ✓
Rule 2: Each COLUMN must have unique digits
Column 0: [5, 6, ., 8, 4, 7, ., ., .]
5, 6, 8, 4, 7 all different ✓
Rule 3: Each 3x3 BOX must have unique digits
Box 0 (top-left):
┌─────┬─────┬─────┐
│ 5 │ 3 │ . │
├─────┼─────┼─────┤
│ 6 │ . │ . │
├─────┼─────┼─────┤
│ . │ 9 │ 8 │
└─────┴─────┴─────┘
5, 3, 6, 9, 8 all different ✓
Board Layout with Box Indices
The 9x9 board is divided into 9 boxes (numbered 0-8):
┌─────────┬─────────┬─────────┐
│ │ │ │
│ Box 0 │ Box 1 │ Box 2 │
│ │ │ │
├─────────┼─────────┼─────────┤
│ │ │ │
│ Box 3 │ Box 4 │ Box 5 │
│ │ │ │
├─────────┼─────────┼─────────┤
│ │ │ │
│ Box 6 │ Box 7 │ Box 8 │
│ │ │ │
└─────────┴─────────┴─────────┘
Box calculation for cell (row, col):
boxIndex = (row / 3) * 3 + (col / 3)
Examples:
(0, 0) → box 0
(0, 5) → box 1
(4, 4) → box 4
(8, 8) → box 8
Invalid Example Visualization
Invalid case: Two 8's in same row
Row 0: [8, 3, ., ., 7, ., ., ., .]
↑
Row 3: [8, ., ., ., 6, ., ., ., 3]
↑
Both in Column 0 → INVALID ✗
Also invalid in Box 0:
┌─────┬─────┬─────┐
│ 8 │ 3 │ . │ ← 8 here
├─────┼─────┼─────┤
│ 6 │ . │ . │
├─────┼─────┼─────┤
│ 8 │ . │ . │ ← 8 here (duplicate!)
└─────┴─────┴─────┘
🎯 Approach 1: Brute Force (Check Each Rule Separately)
The Most Natural Thinking 💭
Core Logic:
Check all three rules independently:
1. Check all rows: For each row, verify no duplicate digits
2. Check all columns: For each column, verify no duplicate digits
3. Check all boxes: For each 3x3 box, verify no duplicate digits
Use HashSet to detect duplicates in each check
Why This Works: - Directly implements the three rules - Easy to understand and debug - Clear separation of concerns
Why This is Suboptimal: - Scans the board 3 times (once per rule) - Creates many temporary HashSets - More code to write and maintain
Implementation
public boolean isValidSudoku(char[][] board) {
// Check all rows
for (int row = 0; row < 9; row++) {
if (!isValidUnit(board, row, 0, 0, 1)) {
return false;
}
}
// Check all columns
for (int col = 0; col < 9; col++) {
if (!isValidUnit(board, 0, col, 1, 0)) {
return false;
}
}
// Check all 3x3 boxes
for (int box = 0; box < 9; box++) {
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
if (!isValidBox(board, startRow, startCol)) {
return false;
}
}
return true;
}
// Check a row or column for duplicates
private boolean isValidUnit(char[][] board, int startRow, int startCol,
int rowDelta, int colDelta) {
HashSet<Character> seen = new HashSet<>();
int row = startRow, col = startCol;
for (int i = 0; i < 9; i++) {
char c = board[row][col];
if (c != '.') {
if (seen.contains(c)) return false;
seen.add(c);
}
row += rowDelta;
col += colDelta;
}
return true;
}
// Check a 3x3 box for duplicates
private boolean isValidBox(char[][] board, int startRow, int startCol) {
HashSet<Character> seen = new HashSet<>();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
char c = board[startRow + i][startCol + j];
if (c != '.') {
if (seen.contains(c)) return false;
seen.add(c);
}
}
}
return true;
}
⏰ Time: O(81) = O(1) - board size fixed, but 3 passes
💾 Space: O(9) = O(1) - HashSets of max size 9
✅ Approach 2: Single Pass with Three HashSets (Optimal)
The Breakthrough Insight 💡
Core Logic:
Key Observation:
We can check all three rules in a SINGLE pass!
Strategy:
Maintain three arrays of HashSets:
- rows[9]: HashSet for each row
- cols[9]: HashSet for each column
- boxes[9]: HashSet for each 3x3 box
For each cell (i, j):
1. If cell is '.', skip it
2. Check if digit already in rows[i] → duplicate!
3. Check if digit already in cols[j] → duplicate!
4. Check if digit already in boxes[boxIndex] → duplicate!
5. If no duplicates, add to all three sets
Box Index Formula:
For cell at (row, col):
boxIndex = (row / 3) * 3 + (col / 3)
Why this works:
- row / 3 gives which row of boxes (0, 1, or 2)
- col / 3 gives which column of boxes (0, 1, or 2)
- Formula maps to 0-8
Examples:
(0,0) → (0/3)*3 + (0/3) = 0*3 + 0 = 0
(0,5) → (0/3)*3 + (5/3) = 0*3 + 1 = 1
(4,4) → (4/3)*3 + (4/3) = 1*3 + 1 = 4
(8,8) → (8/3)*3 + (8/3) = 2*3 + 2 = 8
Why This is Better: - Single pass through board - All checks done simultaneously - Less code, easier to maintain - Early termination on first duplicate
Implementation
public boolean isValidSudoku(char[][] board) {
// Create HashSets for rows, columns, and boxes
HashSet<Character>[] rows = new HashSet[9];
HashSet<Character>[] cols = new HashSet[9];
HashSet<Character>[] boxes = new HashSet[9];
// Initialize all HashSets
for (int i = 0; i < 9; i++) {
rows[i] = new HashSet<>();
cols[i] = new HashSet<>();
boxes[i] = new HashSet<>();
}
// Single pass through the board
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
// Skip empty cells
if (c == '.') continue;
// Calculate which box this cell belongs to
int boxIndex = (i / 3) * 3 + (j / 3);
// Check for duplicates
if (rows[i].contains(c) ||
cols[j].contains(c) ||
boxes[boxIndex].contains(c)) {
return false; // Duplicate found!
}
// Add to all three sets
rows[i].add(c);
cols[j].add(c);
boxes[boxIndex].add(c);
}
}
return true; // No duplicates found
}
Step-by-Step Execution: board with duplicate 8's
Board (simplified view, focusing on key cells):
Row 0: [8, 3, ., ., ., ., ., ., .]
Row 3: [8, ., ., ., ., ., ., ., .]
Initial Setup:
═══════════════════════════════════════════════════════════════════
rows[0..8] = empty HashSets
cols[0..8] = empty HashSets
boxes[0..8] = empty HashSets
═══════════════════════════════════════════════════════════════════
Processing:
Cell (0,0) = '8':
├─ boxIndex = (0/3)*3 + (0/3) = 0
├─ Check rows[0]: empty → no duplicate ✓
├─ Check cols[0]: empty → no duplicate ✓
├─ Check boxes[0]: empty → no duplicate ✓
├─ Add '8' to rows[0]: {8}
├─ Add '8' to cols[0]: {8}
└─ Add '8' to boxes[0]: {8}
Cell (0,1) = '3':
├─ boxIndex = (0/3)*3 + (1/3) = 0
├─ Check rows[0]: {8} → '3' not found ✓
├─ Check cols[1]: empty → no duplicate ✓
├─ Check boxes[0]: {8} → '3' not found ✓
├─ Add '3' to rows[0]: {8, 3}
├─ Add '3' to cols[1]: {3}
└─ Add '3' to boxes[0]: {8, 3}
... (skip empty cells and other cells)
Cell (3,0) = '8':
├─ boxIndex = (3/3)*3 + (0/3) = 1*3 + 0 = 3
├─ Check rows[3]: empty → no duplicate ✓
├─ Check cols[0]: {8, 6, ...} → '8' FOUND! ✗
└─ DUPLICATE IN COLUMN 0!
Return false immediately
═══════════════════════════════════════════════════════════════════
🎯 RESULT: false (found duplicate '8' in column 0)
═══════════════════════════════════════════════════════════════════
Valid Board Example: All checks pass
Board (simplified):
[["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"]]
Cell (0,0) = '5':
rows[0].add('5'), cols[0].add('5'), boxes[0].add('5')
Cell (0,1) = '3':
rows[0].add('3'), cols[1].add('3'), boxes[0].add('3')
... continue for all cells
After scanning entire board:
- No duplicates found in any row
- No duplicates found in any column
- No duplicates found in any box
Result: true ✓
⏰ Time: O(81) = O(1) - single pass, fixed board size
💾 Space: O(81) = O(1) - max 27 HashSets with 9 elements each
🎯 Approach 3: String Encoding (Most Elegant)
Thought Process 💭
Core Logic:
Instead of maintaining separate HashSet arrays,
use a SINGLE HashSet with encoded strings!
Encoding scheme:
- Row check: "5 in row 0"
- Column check: "5 in col 2"
- Box check: "5 in box 4"
All strings go into one HashSet
If string already exists → duplicate!
Why This is Elegant: - Single HashSet instead of 27 - More concise code - Same efficiency - Easy to read and understand
Implementation
public boolean isValidSudoku(char[][] board) {
HashSet<String> seen = new HashSet<>();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
// Create three encoded strings
String row = c + " in row " + i;
String col = c + " in col " + j;
String box = c + " in box " + (i/3)*3 + (j/3);
// Check if any already exist
if (!seen.add(row) || !seen.add(col) || !seen.add(box)) {
return false;
}
}
}
}
return true;
}
Step-by-Step Execution
Cell (0,0) = '5':
├─ Create: "5 in row 0"
├─ Create: "5 in col 0"
├─ Create: "5 in box 0"
├─ Add all three to seen
└─ seen = {"5 in row 0", "5 in col 0", "5 in box 0"}
Cell (0,1) = '3':
├─ Create: "3 in row 0"
├─ Create: "3 in col 1"
├─ Create: "3 in box 0"
├─ Add all three to seen
└─ seen = {..., "3 in row 0", "3 in col 1", "3 in box 0"}
Cell (1,0) = '6':
├─ Create: "6 in row 1"
├─ Create: "6 in col 0"
├─ Create: "6 in box 0"
├─ Try to add to seen
└─ All added successfully (no duplicates)
If we encounter duplicate:
Cell (3,0) = '8' (but '8' already in col 0):
├─ Create: "8 in row 3"
├─ Create: "8 in col 0"
├─ Try to add "8 in col 0" → ALREADY EXISTS!
└─ Return false
⏰ Time: O(1) - fixed 81 cells
💾 Space: O(1) - max 81*3 = 243 strings
🔍 Edge Cases
Case 1: Empty board (all '.')
Input: All cells are '.'
Output: true
Reason: No filled cells to violate rules
Case 2: Single filled cell
Input: One '5', rest are '.'
Output: true
Case 3: Duplicate in same row
Input: ['5', '3', '5', ., ., ., ., ., .]
Output: false
Reason: Two 5's in row 0
Case 4: Duplicate in same column
Input:
Row 0: ['5', ., ., ...]
Row 1: ['5', ., ., ...]
Output: false
Case 5: Duplicate in same box
Input:
┌───┬───┬───┐
│ 5 │ . │ . │
│ . │ 5 │ . │
│ . │ . │ . │
└───┴───┴───┘
Output: false
Case 6: Valid but unsolvable
Input: Valid board that cannot be solved
Output: true
Reason: We only check validity, not solvability
Case 7: All same digit in different regions
Input: '1' in all 9 boxes but different rows/cols
Output: Could be valid if placed correctly
Case 8: Maximum filled (no empty cells)
Input: Complete valid sudoku solution
Output: true
Common Mistakes
Mistake 1: Wrong box index calculation
❌ Wrong: boxIndex = i/3 + j/3
✓ Right: boxIndex = (i/3)*3 + (j/3)
Mistake 2: Not skipping empty cells
❌ Wrong: Check '.' as if it's a digit
✓ Right: if (c == '.') continue;
Mistake 3: Creating new HashSet each time
❌ Wrong: HashSet<Character> set = new HashSet<>(); inside loop
✓ Right: Create once, reuse or use array of sets
Mistake 4: Not checking all three conditions
❌ Wrong: Only check rows and columns
✓ Right: Must check rows, columns, AND boxes
Mistake 5: Off-by-one in box calculation
❌ Wrong: Confusion between 0-indexed and 1-indexed
✓ Right: Use integer division (i/3, j/3)
🎯 Key Takeaways
⚡ Algorithm Comparison
Approach 1: Three Separate Passes
Time: O(1) but 3 passes
Space: O(1)
Use: When code clarity most important
Approach 2: Single Pass with Arrays (RECOMMENDED)
Time: O(1) single pass
Space: O(1)
Use: Best balance of clarity and efficiency
Approach 3: String Encoding (MOST ELEGANT)
Time: O(1)
Space: O(1)
Use: Most concise code
🔑 The Core Insight
Sudoku validation = Three independent checks:
1. Row uniqueness
2. Column uniqueness
3. Box uniqueness
Optimization: Do all checks in ONE pass
→ Use HashSets to detect duplicates
→ Box index formula: (row/3)*3 + (col/3)
Single pass beats three passes
→ Same complexity but faster in practice
🎯 Pattern Recognition
Problem Type: Matrix Validation with Multiple Constraints
Core Pattern: HashSet for duplicate detection
When to Apply:
✓ Need to validate uniqueness in multiple dimensions
✓ Matrix has regions with constraints
✓ Can process in single pass
✓ Fixed size constraints
Key Techniques:
→ HashSet array for tracking seen elements
→ Index formula for mapping cells to regions
→ Early termination on first violation
Related Problems:
- Sudoku Solver (LC 37)
- N-Queens (LC 51)
- Valid Tic-Tac-Toe State (LC 794)
🧠 Interview Strategy
Step 1: "Need to check three rules: rows, columns, boxes"
Step 2: "Can check each rule separately - 3 passes"
Step 3: "Better: check all rules in single pass"
Step 4: "Use HashSets to detect duplicates"
Step 5: "Box index formula: (row/3)*3 + (col/3)"
📝 Quick Revision Notes
🎯 Core Concept:
Validate sudoku by checking no duplicates in any row, column, or 3x3 box. Use HashSets to track seen digits in single pass.
⚡ Quick Implementation:
public boolean isValidSudoku(char[][] board) {
// // Approach 1 - using 27 hashset
// Set<Character>[] rows = new HashSet[9];
// Set<Character>[] cols = new HashSet[9];
// Set<Character>[] box = new HashSet[9];
// // Initialize all 27 hashset
// for(int i = 0; i < 9; i++) {
// rows[i] = new HashSet<>();
// cols[i] = new HashSet<>();
// box[i] = new HashSet<>();
// }
// // Iterate through every cell and fill the above sets and decide.
// for(int i = 0; i < board.length; i++) {
// for(int j = 0; j < board[0].length; j++) {
// char ch = board[i][j];
// if(ch == '.') {
// continue;
// }
// int boxIndex = (i / 3) * 3 + (j / 3);
// if(rows[i].contains(ch) || cols[j].contains(ch) || box[boxIndex].contains(ch)) {
// return false;
// }
// rows[i].add(ch);
// cols[j].add(ch);
// box[boxIndex].add(ch);
// }
// }
// return true;
// Approach 2 - using a single string hashset
HashSet<String> set = new HashSet<>();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(board[i][j] == '.') {
continue;
}
char ch = board[i][j];
int boxIndex = (i / 3) * 3 + (j / 3);
String rowText = ch + " found in row " + i;
String colText = ch + " found in col " + j;
String boxText = ch + " found in box " + boxIndex;
if(set.contains(rowText) || set.contains(colText) || set.contains(boxText)) {
return false;
}
set.add(rowText);
set.add(colText);
set.add(boxText);
}
}
return true;
}
🔑 Key Insights:
- Three rules to check: rows, columns, 3x3 boxes
- Single pass through board checks all rules
- HashSet detects duplicates efficiently
- Box index formula:
(row/3)*3 + (col/3) - String encoding elegant alternative to array of sets
- Skip empty cells (marked with '.')
🎪 Memory Aid:
"One pass, three checks, box formula (row/3)3 + (col/3)!"
Think: "Row check, col check, box check - all in one loop"*
Related Patterns
- Sudoku Solver (LC 37)
- N-Queens (LC 51)
- Valid Tic-Tac-Toe State (LC 794)
Happy practicing! 🎯