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.
A complete visual walkthrough of validating a 9x9 Sudoku board in a single elegant pass.
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:
1-9 without repetition.1-9 without repetition.3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.Note:
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 == 9board[i].length == 9board[i][j] is a digit 1-9 or '.'.The obvious approach validates the board in three separate sweeps:
This works, but we iterate through the board three times. Can we do it in one?
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.
For each cell (i, j) with digit c, we generate three keys:
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.
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:
Three keys added to the set:
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!
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; } }
func isValidSudoku(board [][]byte) bool { seen := make(map[string]bool) for i := 0; i < 9; i++ { for j := 0; j < 9; j++ { c := board[i][j] if c == '.' { continue // skip empty cells } // Build keys for all three constraints rowKey := fmt.Sprintf("%c in row %d", c, i) colKey := fmt.Sprintf("%c in col %d", c, j) boxKey := fmt.Sprintf("%c in box %d-%d", c, i/3, j/3) if seen[rowKey] || seen[colKey] || seen[boxKey] { return false // duplicate found } seen[rowKey] = true seen[colKey] = true seen[boxKey] = true } } return true }
class Solution: def is_valid_sudoku(self, board: list[list[str]]) -> bool: seen: set[str] = set() for i in range(9): for j in range(9): c = board[i][j] if c == ".": continue # skip empty cells # Try to add all three constraints # If any key already exists → duplicate! row_key = f"{c} in row {i}" col_key = f"{c} in col {j}" box_key = f"{c} in box {i // 3}-{j // 3}" if row_key in seen or col_key in seen or box_key in seen: return False seen.add(row_key) seen.add(col_key) seen.add(box_key) return True
use std::collections::HashSet; impl Solution { pub fn is_valid_sudoku(board: Vec<Vec<char>>) -> bool { let mut seen: HashSet<String> = HashSet::new(); for i in 0..9 { for j in 0..9 { let c = board[i][j]; if c == '.' { continue; // skip empty cells } // Build keys for all three constraints let row_key = format!("{} in row {}", c, i); let col_key = format!("{} in col {}", c, j); let box_key = format!("{} in box {}-{}", c, i / 3, j / 3); if !seen.insert(row_key) || !seen.insert(col_key) || !seen.insert(box_key) { return false; // duplicate found } } } true } }
function isValidSudoku(board: string[][]): boolean { const seen = new Set<string>(); for (let i = 0; i < 9; i++) { for (let j = 0; j < 9; j++) { const c = board[i][j]; if (c === ".") continue; // skip empty cells // Try to add all three constraints // If set already has the key → duplicate! const rowKey = `${c} in row ${i}`; const colKey = `${c} in col ${j}`; const boxKey = `${c} in box ${Math.floor(i/3)}-${Math.floor(j/3)}`; if (seen.has(rowKey) || seen.has(colKey) || seen.has(boxKey)) return false; seen.add(rowKey); seen.add(colKey); seen.add(boxKey); } } 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.
Click cells to edit the board, then step through validation. Watch each cell get checked and constraints get added to the set.
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.
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).
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.
Review these before coding to avoid predictable interview regressions.
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.
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.