โ† 2026-03-20 ๐Ÿ“‚ All Days 2026-03-23 โ†’
๐Ÿ”ฌ
๐Ÿ”ฌ Deep Dive
๐Ÿ”ฌ Saturday Deep Dive: Valid Sudoku (15 min read)
โ–ผ

๐Ÿ”ฌ Saturday Deep Dive: Valid Sudoku (15 min read)

๐Ÿ“Š Day 8/150 ยท NeetCode: 8/150 ยท SysDesign: 8/40 ยท Behavioral: 8/40 ยท Frontend: 8/50 ยท AI: 4/30

๐Ÿ”ฅ 8-day streak!

๐Ÿ”— LeetCode #36: Valid Sudoku ๐ŸŸก Medium

๐Ÿ“น NeetCode Solution


Overview / ๆฆ‚่ฟฐ

ไปŠๅคฉๆˆ‘ไปฌๆทฑๅ…ฅ็ ”็ฉถไธ€้“็ปๅ…ธ็š„ไธญ็ญ‰้šพๅบฆ้ข˜๏ผš้ชŒ่ฏๆ•ฐ็‹ฌ็›˜้ข็š„ๅˆๆณ•ๆ€งใ€‚

Today we deep-dive into a classic Medium problem: checking whether a Sudoku board is valid.

่ฟ™้“้ข˜็œ‹่ตทๆฅ็ฎ€ๅ•๏ผŒๅฎžๅˆ™่€ƒๅฏŸไฝ ๅฏนๅคš็ปดๅ“ˆๅธŒใ€ๅๆ ‡ๆ˜ ๅฐ„ๅ’Œ่พน็•Œๆกไปถ็š„ๆŽŒๆกใ€‚

It looks approachable, but tests your grasp of multi-dimensional hashing, coordinate mapping, and edge conditions.

้ข่ฏ•ไธญๅฎƒ้ข‘็นๅ‡บ็Žฐ๏ผŒๅ› ไธบๅฎƒ่€ƒๅฏŸ็š„ไธๆ˜ฏ"ไฝ ่ƒฝไธ่ƒฝๅ†™ๅพช็Žฏ"๏ผŒ่€Œๆ˜ฏ"ไฝ ่ƒฝไธ่ƒฝ็ณป็ปŸๅœฐๅฏนๆ•ฐๆฎๅปบๆจก"ใ€‚

It appears often in interviews not to test loop-writing, but to test systematic data modeling.


Part 1: Theory / ็†่ฎบๅŸบ็ก€ (5 min)

ไป€ไนˆๆ˜ฏๆ•ฐ็‹ฌ้ชŒ่ฏ๏ผŸ/ What Are We Validating?

ไธ€ไธชๅˆๆณ•็š„ๆ•ฐ็‹ฌ็›˜้ขๆปก่ถณไธ‰ไธช็บฆๆŸ๏ผš

A valid Sudoku board satisfies exactly three constraints:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CONSTRAINT 1: Each ROW โ”‚ โ”‚ ๆฏไธ€่กŒ๏ผšๆ•ฐๅญ—1-9ไธ่ƒฝ้‡ๅค โ”‚ โ”‚ No digit 1-9 repeated in a row โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ CONSTRAINT 2: Each COLUMN โ”‚ โ”‚ ๆฏไธ€ๅˆ—๏ผšๆ•ฐๅญ—1-9ไธ่ƒฝ้‡ๅค โ”‚ โ”‚ No digit 1-9 repeated in col โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ CONSTRAINT 3: Each 3ร—3 BOX โ”‚ โ”‚ ๆฏไธช3ร—3ๅฎซๆ ผ๏ผšๆ•ฐๅญ—1-9ไธ่ƒฝ้‡ๅค โ”‚ โ”‚ No digit 1-9 repeated in box โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ๅ…ณ้”ฎ็‚น๏ผšๆˆ‘ไปฌไธ้œ€่ฆ้ชŒ่ฏๆ•ฐ็‹ฌ่ƒฝๅฆ่ขซ่งฃๅ‡บ๏ผŒๅช้œ€่ฆ้ชŒ่ฏๅฝ“ๅ‰ๅทฒๅกซๆ•ฐๅญ—ๆ˜ฏๅฆๅˆๆณ•ใ€‚

Key insight: We don't need to verify the puzzle is solvable โ€” only that existing digits don't violate rules.

'.' ไปฃ่กจ็ฉบๆ ผ๏ผŒๅฟฝ็•ฅไธ่ฎกใ€‚/ '.' means empty cell โ€” skip it.

ๆ•ฐๆฎๅปบๆจก๏ผšๆ ธๅฟƒๆ€ๆƒณ / Data Modeling: The Core Idea

ๆœ€็›ด่ง‚็š„ๆšดๅŠ›่งฃๆณ•ๆ˜ฏ๏ผšๅˆ†ๅˆซๅฏนๆฏ่กŒใ€ๆฏๅˆ—ใ€ๆฏไธชๅฎซๆ ผๅš้ชŒ่ฏ๏ผŒๅตŒๅฅ—ๅพช็Žฏไนๆฌกใ€‚

The naive approach: check rows, columns, and boxes separately โ€” nine nested loops.

ๆ›ดไผ˜้›…็š„ๆ–นๅผ๏ผšไธ€ๆฌก้ๅކ๏ผŒๅŒๆ—ถ็ปดๆŠคไธ‰ไธช้›†ๅˆๅญ—ๅ…ธใ€‚

Better: one pass, three sets of dictionaries updated simultaneously.

For cell (r, c) containing digit d: rows[r] โ†’ must not contain d yet cols[c] โ†’ must not contain d yet boxes[b] โ†’ must not contain d yet where b = (r // 3, c // 3) โ† box index!

่ฟ™ไธช (r // 3, c // 3) ๆ˜ฏๆœฌ้ข˜ๆœ€ไผ˜้›…็š„ๆŠ€ๅทง๏ผšๅฐ†ๅๆ ‡ๆ˜ ๅฐ„ไธบ0-8็š„็›’ๅญ็ดขๅผ•ใ€‚

The (r // 3, c // 3) trick maps any cell to its 3ร—3 box index โ€” this is the elegance of the problem.

Box indices visualized: โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ” โ”‚0,0โ”‚0,1โ”‚0,2โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค โ”‚1,0โ”‚1,1โ”‚1,2โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค โ”‚2,0โ”‚2,1โ”‚2,2โ”‚ โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ row 0-2, col 0-2 โ†’ box (0,0) row 0-2, col 3-5 โ†’ box (0,1) row 3-5, col 6-8 โ†’ box (1,2) ... etc.

Part 2: Step-by-Step Implementation / ไธ€ๆญฅไธ€ๆญฅๅฎž็Žฐ (8 min)

Approach 1: Naive (brute force) โ€” O(9ยฒ) time but ugly

้ชŒ่ฏๆฏ่กŒใ€ๆฏๅˆ—ใ€ๆฏๅฎซๆ ผ้œ€่ฆ9ๆฌก็‹ฌ็ซ‹ๅพช็Žฏใ€‚ๅฏ่ฏปๆ€งๅทฎ๏ผŒๅฎนๆ˜“ๅ‡บ้”™ใ€‚

Nine separate loops for rows, columns, boxes. Hard to read, error-prone.

# Approach 1: Naive โ€” DO NOT use in interview (too verbose)
def isValidSudoku_naive(board):
    # Check rows
    for row in board:
        seen = set()
        for c in row:
            if c == '.': continue
            if c in seen: return False
            seen.add(c)
    
    # Check cols (transpose and repeat)
    for col in range(9):
        seen = set()
        for row in range(9):
            c = board[row][col]
            if c == '.': continue
            if c in seen: return False
            seen.add(c)
    
    # Check 3x3 boxes
    for box_row in range(3):
        for box_col in range(3):
            seen = set()
            for r in range(box_row*3, box_row*3+3):
                for c in range(box_col*3, box_col*3+3):
                    val = board[r][c]
                    if val == '.': continue
                    if val in seen: return False
                    seen.add(val)
    return True
# Time: O(81) = O(1) technically since board is fixed 9x9
# Space: O(9) per iteration
# Problem: 3 separate passes, harder to extend, verbose

Approach 2: Optimal โ€” Single Pass, Three Dicts โœ…

่ฟ™ๆ˜ฏ้ข่ฏ•ไธญๅบ”่ฏฅ็ป™ๅ‡บ็š„็ญ”ๆกˆใ€‚ / This is the answer you should give in an interview.

from collections import defaultdict

def isValidSudoku(board):
    # Three dictionaries: rows, columns, boxes
    # Each maps an index to a set of seen digits
    rows = defaultdict(set)   # rows[r] = {digits seen in row r}
    cols = defaultdict(set)   # cols[c] = {digits seen in col c}
    boxes = defaultdict(set)  # boxes[(r//3, c//3)] = {digits seen in box}

    for r in range(9):
        for c in range(9):
            val = board[r][c]
            
            # Skip empty cells
            if val == '.':
                continue
            
            # Calculate box index โ€” THE KEY TRICK
            box_key = (r // 3, c // 3)
            
            # Check all three constraints simultaneously
            if (val in rows[r] or
                val in cols[c] or
                val in boxes[box_key]):
                return False  # Duplicate found โ€” invalid!
            
            # Add to all three tracking sets
            rows[r].add(val)
            cols[c].add(val)
            boxes[box_key].add(val)
    
    return True  # No violations found

Visual Trace / ้€ๆญฅ่ฟฝ่ธช

Let's trace through a small part of the board:

Board (first 3 rows shown):
["5","3",".",  ".","7",".",  ".",".","."]
["6",".",  ".",  "1","9","5",  ".",".","."]
[".","9","8",  ".",".",  ".",  ".","6","."]

Step-by-step trace:

(r=0, c=0) val='5': box_key = (0//3, 0//3) = (0,0) rows[0]={}, cols[0]={}, boxes[(0,0)]={} โ†’ no conflict Add: rows[0]={'5'}, cols[0]={'5'}, boxes[(0,0)]={'5'} (r=0, c=1) val='3': box_key = (0,0) rows[0]={'5'}, '3' not in it โœ“ cols[1]={}, '3' not in it โœ“ boxes[(0,0)]={'5'}, '3' not in it โœ“ Add: rows[0]={'5','3'}, cols[1]={'3'}, boxes[(0,0)]={'5','3'} (r=0, c=2) val='.': SKIP (r=0, c=4) val='7': box_key = (0//3, 4//3) = (0,1) โ† different box! Add to rows[0], cols[4], boxes[(0,1)] (r=1, c=0) val='6': box_key = (1//3, 0//3) = (0,0) โ† same box as row 0! rows[1]={}, '6' not in it โœ“ cols[0]={'5'}, '6' not in it โœ“ boxes[(0,0)]={'5','3'}, '6' not in it โœ“ Add successfully. Imagine if we had another '5' at (1,0): cols[0] already has '5' โ†’ return False immediately! โœ…

Part 3: Edge Cases & Gotchas / ่พน็•Œๆƒ…ๅ†ต (2 min)

Edge Case 1: Empty Board

board = [['.']*9 for _ in range(9)]
# Result: True โ€” empty board is valid
# Our code handles this: all '.' cells are skipped

Edge Case 2: Same digit in same box, different row AND column

# Trap: this is invalid even though row 0 and col 3 look different
# '5' at (0,0) and '5' at (2,2) โ€” same 3ร—3 box!
# The box_key trick catches this: both map to (0,0)

Edge Case 3: The "it looks valid row-wise" trap

# Each row and column could be valid individually,
# but a 3ร—3 box might have duplicates
# Always check all THREE constraints โ€” don't shortcut!

ๅธธ่ง้ข่ฏ•ๅ‘ / Common Interview Traps

โŒ Trap 1: Using (r // 3) * 3 + (c // 3) as box index โ†’ This creates 0-8 integer keys, works but harder to read โ†’ Tuple (r//3, c//3) is cleaner and equally correct โŒ Trap 2: Validating the full 1-9 range โ†’ We only need to check for duplicates among present digits โ†’ If a row has [1,2,3,...,8] with one empty, it's still valid! โŒ Trap 3: Modifying the input board โ†’ Never do this. The problem says "determine if valid", not "solve it" โœ… Best practice: defaultdict(set) avoids KeyError on first access โœ… Best practice: check all 3 constraints before adding โ€” order matters!

Part 4: Real-World Application / ๅฎž้™…ๅบ”็”จ (2 min)

ๆ•ฐ็‹ฌ้ชŒ่ฏๅ™จไธๅชๆ˜ฏไธ€้“้ข่ฏ•้ข˜โ€”โ€”ๅฎƒ็š„ๅบ•ๅฑ‚ๆ€่ทฏๅœจๅพˆๅคšๅœฐๆ–น้ƒฝๆœ‰ๅบ”็”จใ€‚

Valid Sudoku isn't just an interview puzzle โ€” its core pattern appears everywhere.

Pattern: Multi-Key Set Membership Check

1. Database Uniqueness Constraints

In databases, a UNIQUE constraint across multiple columns is the same idea:
  UNIQUE(user_id, date) โ€” same as "no two rows share both values"
  
PostgreSQL internally uses hash indexes (like our sets) per constraint.

2. Spreadsheet Validation

Google Sheets "no duplicates in range" validation:
  Checks each row, column, named range simultaneously
  Exact same three-constraint structure

3. Compiler Symbol Tables

A compiler tracks variable names per scope: rows โ†’ local scope cols โ†’ class scope boxes โ†’ module scope "Variable already declared" = duplicate in the same scope (set)

4. Form Validation in UI

// Same pattern: check uniqueness across multiple dimensions
const seen = { byEmail: new Set(), byUsername: new Set() };
users.forEach(user => {
  if (seen.byEmail.has(user.email)) throw Error("duplicate email");
  seen.byEmail.add(user.email);
});

Part 5: Interview Simulation / ้ข่ฏ•ๆจกๆ‹Ÿ (3 min)

ๅ‡่ฎพไฝ ๅทฒ็ป็ป™ๅ‡บไบ†ๆœ€ไผ˜่งฃใ€‚้ข่ฏ•ๅฎ˜ๅฏ่ƒฝไผš้—ฎ่ฟ™ไบ›้—ฎ้ข˜๏ผš

Assume you've presented the optimal solution. Here are follow-up questions interviewers typically ask:


Q1: "What's the time and space complexity?"

Time: O(81) = O(1) โ†’ Fixed 9ร—9 board: exactly 81 cells, constant work per cell โ†’ In general: O(nยฒ) for an nร—n Sudoku variant Space: O(81) = O(1) โ†’ At most 81 entries across all sets (each cell appears once per set) โ†’ In general: O(nยฒ) โš ๏ธ Interviewer follow-up: "But you're using three defaultdicts..." โ†’ Still O(1) because board size is fixed. For variable n, it's O(nยฒ).

Q2: "Could you do this without any extra space?"

ไธๅฎŒๅ…จ่ƒฝ๏ผŒไฝ†ๅฏไปฅๅŽ‹็ผฉใ€‚/ Not quite, but you can compress.

You could encode sets as bitmasks (bit 1 = digit 1 seen, bit 2 = digit 2, etc.):
  rows = [0] * 9   # 9 integers, each bit i = digit i+1 seen
  cols = [0] * 9
  boxes = [0] * 9

  For digit d (1-9), check: if rows[r] & (1 << d): return False
                            rows[r] |= (1 << d)

Space: O(27 integers) = O(1). Faster in practice due to cache locality.

Q3: "How would you extend this to validate a 16ร—16 Sudoku (Hexadoku)?"

# Same code, parameterized:
def isValidSudokuNxN(board, n=9, box_size=3):
    rows = defaultdict(set)
    cols = defaultdict(set)
    boxes = defaultdict(set)
    for r in range(n):
        for c in range(n):
            val = board[r][c]
            if val == '.': continue
            box_key = (r // box_size, c // box_size)
            if val in rows[r] or val in cols[c] or val in boxes[box_key]:
                return False
            rows[r].add(val); cols[c].add(val); boxes[box_key].add(val)
    return True
# For 16ร—16: n=16, box_size=4 โ†’ same logic, bigger constants

Q4: "What if you needed to not just validate but also solve the Sudoku?"

้ชŒ่ฏๆ˜ฏๆฑ‚่งฃ็š„ๅญ้—ฎ้ข˜ใ€‚/ Validation is a sub-problem of solving.

Solving requires backtracking:
1. Find an empty cell
2. Try digits 1-9
3. For each digit: call isValid (our function!) to check constraints
4. If valid, place digit, recurse
5. If recursion fails, backtrack (remove digit, try next)

Time: O(9^m) where m = number of empty cells โ€” exponential worst case
Typical Sudoku (m~50 empty cells) solves in milliseconds due to pruning.

Q5: "If this were a production system checking millions of Sudoku boards per second, what would you optimize?"

1. Bitmask instead of sets โ†’ 9 ints vs 27 sets, better cache performance 2. Early termination โ†’ return False as soon as first conflict found (already done!) 3. SIMD/vectorization โ†’ process 4 rows simultaneously with 256-bit registers 4. GPU parallelism โ†’ each 81-cell check is independent; batch 10k boards on GPU 5. Pre-compute valid digit sets โ†’ use lookup tables for box โ†’ allowed digits In practice: bitmask version on CPU is 3-5x faster than set version.

Complexity Summary / ๅคๆ‚ๅบฆๆ€ป็ป“

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Approach โ”‚ Time โ”‚ Space โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Naive (3 loops) โ”‚ O(81) โ”‚ O(27) sets โ”‚ โ”‚ Single pass โ”‚ O(81) โ”‚ O(27) sets โ”‚ โ† Best readability โ”‚ Bitmask โ”‚ O(81) โ”‚ O(27) ints โ”‚ โ† Best performance โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ All O(1) for fixed 9ร—9 board. For general nร—n Sudoku: O(nยฒ) time and space.

๐Ÿ“š References / ๆทฑๅ…ฅๅญฆไน 

โ€ข ๐Ÿ”— LeetCode #36: Valid Sudoku ๐ŸŸก Medium

โ€ข ๐Ÿ“น NeetCode Solution Video

โ€ข ๐Ÿ“– NeetCode Arrays & Hashing Roadmap โ€” Visual learning path for this pattern

โ€ข ๐Ÿ”— LeetCode #37: Sudoku Solver โ€” Natural follow-up (backtracking)

โ€ข ๐Ÿ”— LeetCode #187: Repeated DNA Sequences โ€” Same multi-key hashing pattern


๐Ÿง’ ELI5: Checking if a Sudoku is valid is like making sure no two kids in the same row, column, or team have the same number on their shirt โ€” you write down each number as you see it, and yell "STOP!" if you see it twice in the same group.


Saturday Deep Dive โ€” Day 8 ยท Generated 2026-03-21