Start learning free

LESSON 323 OF 574 · HASH TABLE

Valid Sudoku in Swift: One Pass with Sets, O(81) Time

Practice O(81) Read the full explanation

A Sudoku board is a 9 x 9 grid of cells. Each cell holds a digit from '1' to '9', or '.' while it is empty. Rows 0-2, 3-5 and 6-8 and columns 0-2, 3-5 and 6-8 split the board into nine 3 x 3 boxes.

Given a partly filled board, return true if it is valid so far, and false otherwise. The board is valid when no digit appears twice:

  1. in the same row,
  2. in the same column,
  3. in the same 3 x 3 box.

Only the filled cells are checked; empty cells don't count. A valid board doesn't have to be solvable: you check the digits already on it, you don't solve the puzzle.

Rows are numbered from 0 at the top and columns from 0 at the left. In the examples, each line is one row of the board.

 

Example 1:



Input: board =
53..7....
6..195...
.98....6.
8...6...3
4..8.3..1
7...2...6
.6....28.
...419..5
....8..79
Output: true
Explanation: No row, column or box holds the same digit twice.

Example 2:

Input: board =
83..7....
6..195...
.98....6.
8...6...3
4..8.3..1
7...2...6
.6....28.
...419..5
....8..79
Output: false
Explanation: The board of Example 1 with an 8 instead of the 5 in the top left corner.
Column 0 now holds two 8s, in rows 0 and 3, and so does the top left box, in rows 0 and 2.

Example 3:

Input: board =
12345678.
.........
.........
.........
........9
.........
.........
.........
.........
Output: true
Explanation: No digit repeats, so the board is valid, although it has no solution:
the last cell of row 0 can only be a 9, and column 8 already has a 9 in row 4.

 

Constraints:

  • The board has 9 rows, and each row has 9 cells.
  • Each cell holds a digit from '1' to '9' or '.'.

Note:

Your algorithm should look at each cell once and check it in constant time: O(81) time and O(81) extra space for the 81 cells. Scanning the row, the column and the box of every cell again would also work on a board this small, but it looks at each cell many times.

STEP 1 OF 11
    Ctrl + Enter to check

    SWIFT · HASH TABLE

    Valid Sudoku in Swift, explained

    Problem overview

    The method isValidSudoku receives a 9 x 9 board, a [[Character]] in which each cell is a digit from "1" to "9" or "." for an empty cell. It returns true when no digit appears twice in the same row, the same column or the same 3 x 3 box. Only the filled cells are checked. The board doesn't have to be solvable: you check the digits that are already there, you don't solve the puzzle.

    The first example is a well-known partly filled board with no repeated digit, so the answer is true. In the second, the 5 in the top left corner is replaced by an 8. Column 0 now holds two 8s, in rows 0 and 3, and so does the top left box, in rows 0 and 2, so the answer is false. The third board has the digits 1 to 8 in row 0 and a single 9 in row 4, column 8. Nothing repeats, so it is valid even though no solution exists: the last cell of row 0 could only be a 9, and column 8 already has one.

    Approach: remember what each row, column and box has seen

    Checking a cell means asking three questions: is this digit already in my row, in my column, in my box? Scanning the row, the column and the box again for every cell works on a board this small, but it looks at each cell many times over.

    A better way keeps a memory. Create one set of digits per row, per column and per box: 27 sets in all. Then visit the cells once. For each filled cell, look the digit up in its three sets. If any of them has it, the digit repeats and the board is invalid. Otherwise add the digit to all three, so later cells can find it. A set lookup and an insertion take constant time, so the whole check is one pass over 81 cells.

    The only piece of arithmetic is the box number. The boxes are numbered 0 to 8, left to right and then top to bottom. A cell in row r and column c is in box row r / 3 and box column c / 3, where / on two Int values drops the remainder. Three boxes fit in each band, so the box number is (r / 3) * 3 + c / 3. For example, the cell at row 4, column 7 has 4 / 3 = 1 and 7 / 3 = 2, so it lies in box 1 * 3 + 2 = 5, the right box of the middle band. The corners give 0 for (0, 0) and 8 for (8, 8).

    Step by step

    1. Create the row sets. rows is an array of 9 empty Set<Character>, built with [Set<Character>](repeating: Set<Character>(), count: 9). rows[r] holds the digits seen so far in row r.
    2. Create cols and boxes the same way: nine empty sets each, one per column and one per box.
    3. Loop over the rows and, inside, the columns, with r and c going over 0..<9. This visits every cell, row by row.
    4. Read the cell into digit.
    5. Skip empty cells. If digit is ".", continue with the next cell. Empty cells never break a rule, however many there are.
    6. Compute the box number as described above.
    7. Look for a repeat. If rows[r], cols[c] or boxes[box] already contains digit, return false. Each set holds the digits of the cells visited before this one, so finding the digit there means it appears twice.
    8. Remember the digit. Insert it into all three sets.
    9. Return true after the loops. Every filled cell was checked against the earlier ones and nothing repeated.

    The solution in Swift

    class Solution {
        func isValidSudoku(_ board: [[Character]]) -> Bool {
            var rows = [Set<Character>](repeating: Set<Character>(), count: 9)
            var cols = [Set<Character>](repeating: Set<Character>(), count: 9)
            var boxes = [Set<Character>](repeating: Set<Character>(), count: 9)
            for r in 0..<9 {
                for c in 0..<9 {
                    let digit = board[r][c]
                    if digit == "." { continue }
                    let box = (r / 3) * 3 + c / 3
                    if rows[r].contains(digit) || cols[c].contains(digit) || boxes[box].contains(digit) {
                        return false
                    }
                    rows[r].insert(digit)
                    cols[c].insert(digit)
                    boxes[box].insert(digit)
                }
            }
            return true
        }
    }
    

    The Swift details worth knowing:

    • Sets are value types. repeating:count: fills the array with 9 copies of the empty set, which sounds like one shared set. In Swift, sets, like arrays and strings, are copied when they are assigned or stored, so each slot is an independent set. Inserting into rows[0] leaves rows[1] empty. In a language where collections are references, this construction would share one set.
    • Character, not String. The cells are Character values. A literal such as "." becomes a Character because the comparison digit == "." has a Character on its left. The digits are never turned into numbers: "5" is only compared and stored as a character.
    • board[r][c]. The board is an array of rows, so the row index comes first. Each row is itself an array of nine characters.
    • insert mutates in place. rows[r].insert(digit) changes one element of the rows array, which works because rows is declared with var.
    • Integer division. r / 3 on Int truncates toward zero, so 7 / 3 is 2, not 2.33.
    • The one-line continue. In Swift, the braces of if are required, so a one-line form is written if digit == "." { continue }.

    Complexity

    Time: O(81), which is constant. The two loops visit 81 cells, and each visit does three set lookups and three insertions, each of which takes constant time.

    Space: O(81), also constant. The 27 sets hold at most 9 digits each, and every filled cell adds a digit to three sets, so at most 243 entries exist at once. The size does not depend on any input, because the board always has the same shape.

    Edge cases and common mistakes

    • Counting empty cells. If "." goes into the sets, the second empty cell in a row looks like a repeat. Skip them first.
    • A wrong box formula. r / 3 + c / 3 gives the same number for different boxes. For instance box (0, 1) and box (1, 0) would both be 1. Multiply the box row by 3.
    • Checking only one rule. A digit repeated in a box only can still be valid for rows and columns. For example, a 5 at row 3, column 3 and another at row 4, column 4 break only the box rule, and the lesson's code returns false for them.
    • Same digit in unrelated cells. A 5 at row 0, column 0 and a 5 at row 1, column 3 share no row, column or box, so the board is still valid.
    • Confusing valid with solvable. The method never tries to complete the puzzle. The third example is valid and has no solution.

    Testing

    • The first example board (the one with a 5 in the top left corner) gives true.
    • The same board with an 8 in the top left corner gives false.
    • The board with the digits 1 to 8 in row 0 and a 9 at row 4, column 8, gives true.
    • A board of 81 dots gives true.
    • A board with 5...5.... in row 0 and dots elsewhere gives false: a repeat in a row.
    • A board with a 5 at the start of row 0 and another at the start of row 8 gives false: a repeat in a column.
    • A board with a 5 at row 3, column 3 and another at row 4, column 4 gives false: a repeat in a box only.
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free