LeetCode #36 — Medium

Valid Sudoku

A complete visual walkthrough of validating a 9x9 Sudoku board in a single elegant pass.

Solve on LeetCode
The Problem

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:

  1. Each row must contain the digits 1-9 without repetition.
  2. Each column must contain the digits 1-9 without repetition.
  3. Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without 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 '.'.

Roadmap

  1. The Brute Force — Three Separate Passes
  2. The Core Insight — One Pass with Encoded Keys
  3. Walkthrough — Visualizing the Grid
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

The Brute Force — Three Separate Passes

The obvious approach validates the board in three separate sweeps:

Three Passes

Pass 1: Check Rows
For each of the 9 rows, check that no digit appears more than once.
Pass 2: Check Columns
For each of the 9 columns, check that no digit appears more than once.
Pass 3: Check 3x3 Boxes
For each of the 9 boxes, check that no digit appears more than once.

This works, but we iterate through the board three times. Can we do it in one?

Step 02

The Core Insight — One Pass with Encoded Keys

The elegant trick: encode each constraint as a unique string and add it to a single HashSet. If set.add() returns false, we found a duplicate.

The Encoding Scheme

For each cell (i, j) with digit c, we generate three keys:

c "5 in row 3" row constraint
c "5 in col 7" column constraint
c "5 in box 1-2" box constraint (i/3 - j/3)

If any set.add() returns false, the string already existed, meaning a duplicate digit was found in that row, column, or box.

💡

Why i/3 and j/3? Integer division maps rows 0-2 to box row 0, rows 3-5 to box row 1, rows 6-8 to box row 2. Same for columns. So "box 1-2" uniquely identifies one of the nine 3x3 boxes.

Step 03

Walkthrough — Visualizing the Grid

Let's see how the algorithm processes a partially filled board. When we reach cell (0,0) with digit "5", we add three encoded strings to the set:

Processing Cell (0, 0) = "5"

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

Three keys added to the set:

"5 in row 0"
"5 in col 0"
"5 in box 0-0"
Row 0
Column 0
Box 0-0

If later we encounter another "5" in row 0, the string "5 in row 0" would already be in the set, and set.add() returns false — duplicate found!

Step 04

Edge Cases

Step 05

Full Annotated Code

class Solution {
    public boolean isValidSudoku(char[][] board) {

        Set<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 == '.') continue;    // skip empty cells

                // Try to add all three constraints
                // If any add() returns false → duplicate!
                if (!seen.add(c + " in row " + i) ||
                    !seen.add(c + " in col " + j) ||
                    !seen.add(c + " in box " + i/3 + "-" + j/3))
                    return false;
            }
        }
        return true;
    }
}

Alternative approach: Instead of a single set with encoded strings, you could use boolean[9][9] arrays for rows, columns, and boxes — mapping digit d to index d-1. This avoids string allocation and is slightly faster, but the HashSet approach is more elegant and easier to remember.

Step 06

Interactive Demo

Click cells to edit the board, then step through validation. Watch each cell get checked and constraints get added to the set.

Sudoku Validator

Step 07

Complexity Analysis

Time
O(C)
Space
O(C)

The board is always 9x9 -- a fixed size. We check each of the 81 cells exactly once using hash sets for rows, columns, and boxes. We store at most 243 encoded strings (3 per filled cell). Since the input size is bounded, both time and space are O(81) = O(1) -- constant.

Approach Breakdown

BRUTE FORCE
O(1) time
O(1) space

Three separate passes over the fixed 9x9 board: one each for rows, columns, and 3x3 boxes. Each pass uses a temporary set per group to detect duplicates. Since the board size is constant (81 cells), both time and space are O(1).

OPTIMAL
O(1) time
O(1) space

A single pass visits each of the 81 cells exactly once, encoding three constraint keys per filled cell into one HashSet. At most 243 strings are stored. The fixed board size makes both time and space O(1).

🎯

Both are O(1) because the board is fixed 9x9. In interviews, clarify whether "n" means the board size or is always 9. If the board were N x N, time and space would both be O(N2). The key insight is that our single-pass hash set approach visits each cell exactly once.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Mutating counts without cleanup

Wrong move: Zero-count keys stay in map and break distinct/count constraints.

Usually fails on: Window/map size checks are consistently off by one.

Fix: Delete keys when count reaches zero.

Off-by-one on range boundaries

Wrong move: Loop endpoints miss first/last candidate.

Usually fails on: Fails on minimal arrays and exact-boundary answers.

Fix: Re-derive loops from inclusive/exclusive ranges before coding.