Problem overview
The function isValidSudoku receives board, a 9 x 9 grid stored as [][]byte. Each cell holds a digit character from '1' to '9', or '.' when it is empty. It returns true when no digit appears twice in the same row, the same column or the same 3 x 3 box, and false otherwise.
Only the filled cells are checked. A board can be valid and still impossible to finish: the third example of the lesson has 1 to 8 in row 0 and a 9 at the end of row 4. The last cell of row 0 could only be a 9, but column 8 already has one. No digit repeats, so the function returns true. The function checks the digits that are on the board; it doesn't solve the puzzle.
In the first example, no row, column or box repeats a digit, so the answer is true. In the second, the 5 in the top left corner is replaced by an 8. Column 0 then 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.
Approach: remember what each row, column and box has seen
A direct way is to scan the row, the column and the box of every filled cell again. On a 9 x 9 board that is fast enough, but it looks at each cell many times. A better way is to visit each cell once, and to keep a set of the digits seen so far for every row, every column and every box. When a digit is already in one of the three sets that its cell belongs to, the board is invalid. Otherwise, the digit goes into all three sets.
That is 27 sets in all: 9 for rows, 9 for columns, 9 for boxes. Every cell belongs to exactly one row, one column and one box, so each cell is checked in constant time. The total work is proportional to the 81 cells, and the sets hold at most 81 digits per kind, which is O(81) time and space. Since the board size is fixed, this is the same as O(1), but the O(81) figure shows where the work goes.
Numbering the boxes
Rows and columns have natural numbers, and boxes need one too. The box that holds cell (r, c) is in box row r / 3 and box column c / 3, both 0, 1 or 2, because integer division drops the remainder. Numbering the boxes left to right, then top to bottom gives (r/3)*3 + c/3. For example:
[0 0] 0
[4 7] 5
[8 8] 8
[2 3] 1
[3 2] 3
Each line shows a cell and its box number. The cell in row 4, column 7 is in box 5, the right box of the middle band.
Step by step
- Create the three slices of sets. Go has no set type, so a
map[byte]boolis the usual stand-in.rows,colsandboxesare each made withmakeas a slice of 9 such maps. - Fill every slot with an empty map. A new slice of maps holds nine
nilmaps, and writing to anilmap crashes the program with a panic. A loop setsrows[i],cols[i]andboxes[i]tomap[byte]bool{}. Each row, column and box needs a map of its own. - Visit every cell, row by row. Two nested
forloops, withrandcfrom 0 to 8. - Read the cell into
digit. It is abyte, because the board is a slice of byte slices. - Skip empty cells. If
digitis'.',continuegoes straight to the next cell. Empty cells never break a rule. - Compute the box number with the formula above and store it in
box. - Look for a repeat. If
rows[r][digit],cols[c][digit]orboxes[box][digit]is alreadytrue, returnfalse. Each map holds the digits of the cells visited earlier in that row, column or box. - Record the digit by setting all three entries to
true, so later cells find it. - Return
trueafter both loops: every filled cell was checked against the earlier ones and nothing repeated.
The solution in Go
func isValidSudoku(board [][]byte) bool {
rows := make([]map[byte]bool, 9)
cols := make([]map[byte]bool, 9)
boxes := make([]map[byte]bool, 9)
for i := 0; i < 9; i++ {
rows[i] = map[byte]bool{}
cols[i] = map[byte]bool{}
boxes[i] = map[byte]bool{}
}
for r := 0; r < 9; r++ {
for c := 0; c < 9; c++ {
digit := board[r][c]
if digit == '.' {
continue
}
box := (r/3)*3 + c/3
if rows[r][digit] || cols[c][digit] || boxes[box][digit] {
return false
}
rows[r][digit] = true
cols[c][digit] = true
boxes[box][digit] = true
}
}
return true
}
Go details worth knowing
- Reading a missing key is safe. A map returns the zero value for a key it doesn't have, and the zero value of
boolisfalse. Sorows[r][digit]isfalsefor a digit not seen yet, with no need for the two-value formv, ok := m[k]. Writing is different: anilmap panics with "assignment to entry in nil map", which is why the first loop creates every map. Reading from anilmap is fine, too. - The cell is a
byte, not a string.board[r][c]is abyteholding the character's code. Comparing it with'.'works because a rune literal such as'.'is an untyped constant that converts tobyte. For the digit '5' the byte value is 53, not 5. Nothing needs the digit's numeric value here, since the byte is only a key in the map. - Integer division truncates.
r/3andc/3drop the remainder, so rows 0 to 2 all give 0, rows 3 to 5 give 1, and rows 6 to 8 give 2. The parentheses in(r/3)*3are clearer than they are required. - Short-circuit
||. The three lookups run left to right and stop at the firsttrue. - Maps in a slice. A
[]map[byte]boolstores references to maps, sorows[r][digit] = truechanges the map inside the slice. No copy is made.
Complexity
Time: O(81). The loops visit each of the 81 cells once, and every lookup or insertion into a map takes constant time on average.
Space: O(81). The 27 maps together hold at most three entries per filled cell, so at most 243 entries. They are small, and the board size doesn't change.
Edge cases and common mistakes
- Sharing one map. If all rows used the same map, a 5 in row 0 would make a 5 in row 7 look like a repeat. That is why each row, column and box gets its own.
- Forgetting to skip
'.'. Two empty cells in a row would look like a repeated digit. Withoutcontinue, the empty board would be reported as invalid. - Checking only rows and columns. A board with a 5 at (0, 0) and another at (1, 1) has no repeat in any row or column, but both are in box 0. It returns
falseonly because of the box check. - A wrong box formula.
r/3 + c/3gives the same number to different boxes: boxes 1 and 3 would both be 1. The multiplication by 3 is what makes the numbers unique. - Trying to solve the puzzle. Valid doesn't mean solvable. The function must accept a board that has no solution, as long as nothing repeats.
Testing
- The first example of the lesson gives
true. - The same board with an 8 in the top left corner gives
false. - The unsolvable third example gives
true. - An empty board (81 dots) gives
true. - A board with a 5 in row 0, column 0 and another 5 in row 1, column 1 gives
false: same box. - A board with a 5 in row 0, column 0 and another in row 3, column 0 gives
false: same column. - A board with a 5 in row 0, column 0 and another in row 0, column 8 gives
false: same row.
