Problem overview
The method isValidSudoku receives a 9 x 9 char[][] board. Each cell holds a digit from '1' to '9', or '.' when it is empty. The method 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 filled cells count, and the board does not have to be solvable: you check the digits already on it, you don't solve the puzzle.
The statement's first board is valid, and the method returns true. Replace its top-left 5 with an 8 and column 0 holds two 8s (rows 0 and 3), as does the top-left box (rows 0 and 2), so the method returns false. A third board has the digits 1 to 8 in row 0 and a 9 at row 4, column 8. It has no solution, because the last cell of row 0 could only be a 9 and column 8 already has one, but no digit repeats, so the method returns true.
Approach: remember what each row, column and box has seen
You could scan the row, the column and the box of every cell again and again. On a 9 x 9 board that is fast enough, but it looks at each cell many times. A better idea is to visit each cell once and keep a set of the digits seen so far in every row, every column and every box. A cell is a problem when its digit is already in one of its three sets.
That is 27 sets: 9 for rows, 9 for columns, 9 for boxes. Every cell belongs to exactly one row, one column and one box. Since the board has a fixed size of 81 cells, the work is constant, and the lesson's notes call it O(81) time and space.
Numbering the boxes
Rows and columns number themselves: use r and c. For boxes you need a formula. The box's row is r / 3 and its column is c / 3. Number the boxes left to right, then top to bottom, and the number is (r / 3) * 3 + c / 3. Integer division drops the remainder: 7 / 3 is 2 in Java, while 7 / 3.0 is 2.3333333333333335. These cells show the formula:
cell (0, 0) -> box 0
cell (0, 8) -> box 2
cell (4, 7) -> box 5
cell (4, 4) -> box 4
cell (8, 8) -> box 8
cell (2, 3) -> box 1
Step by step
- Create the row sets.
rowsis aList<Set<Character>>built as a newArrayList.rows.get(r)will hold the digits seen so far in rowr. A set ofcharvalues needs the wrapper typeCharacter, because generics can't take primitives. - Create
colsandboxesthe same way. - Fill the three lists with a loop of nine turns. They start empty, and
get(r)on an empty list would throw. - Add a new
HashSettorowsinside that loop. The diamondnew HashSet<>()takes its element type from the list it goes into. - Add one to
colsand one toboxestoo. Each row, column and box needs a set of its own. - Loop over the rows with
rfrom 0 to 8. - Loop over the columns of row
rwithcfrom 0 to 8. - Store the cell
board[r][c]in acharnameddigit. - Skip empty cells. If
digitis'.',continueto the next column. Empty cells never break the rules. - Compute the box number into an
int boxwith the formula above. - Check for a repeat. If any of the three sets already contains
digit, returnfalse. Each set holds the digits of the cells visited before this one in that row, column or box, so finding the digit there means it appears twice. - Record the digit in the row's set.
- Record it in the column's and the box's sets too.
- Return
trueafter both loops. Every filled cell was checked against the ones before it, and nothing repeated.
The solution in Java
class Solution {
public boolean isValidSudoku(char[][] board) {
List<Set<Character>> rows = new ArrayList<>();
List<Set<Character>> cols = new ArrayList<>();
List<Set<Character>> boxes = new ArrayList<>();
for (int i = 0; i < 9; i++) {
rows.add(new HashSet<>());
cols.add(new HashSet<>());
boxes.add(new HashSet<>());
}
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
char digit = board[r][c];
if (digit == '.') continue;
int box = (r / 3) * 3 + c / 3;
if (rows.get(r).contains(digit) || cols.get(c).contains(digit) || boxes.get(box).contains(digit)) {
return false;
}
rows.get(r).add(digit);
cols.get(c).add(digit);
boxes.get(box).add(digit);
}
}
return true;
}
}
Java details worth knowing:
- Boxing of
char. Callingcontains(digit)on aSet<Character>boxes thecharinto aCharacter. The type matters: afterset.add('5'),set.contains('5')istrue, butset.contains((int) '5')andset.contains(5)are bothfalse, because anIntegeris never equal to aCharacter. - Digits are characters, not numbers.
'5'is the character with code 53. The solution never does arithmetic on digits, only compares them, so it doesn't need to convert them. - Short-circuit
||. The threecontainschecks stop at the firsttrue. - Single-statement
continue.if (digit == '.') continue;skips the rest of that column's turn, and the loop moves on to the nextc.
Complexity
Time: O(81), that is, constant. The board always has 81 cells, each visited once, and each visit does a few set lookups and insertions, which take constant time on average. For a board of n x n cells it would be O(n²).
Space: O(81). The 27 sets together hold at most one entry per filled cell in each of its three sets, so at most 243 entries.
Edge cases and common mistakes
- Checking only rows and columns. The box rule is easy to forget. A 5 at row 0, column 0 and another at row 1, column 1 share no row and no column, yet the board is invalid: the method returns
falsefor it. - The same digit in unrelated cells. A 5 at (0, 0) and a 5 at (4, 4) share no row, column or box, so the method returns
true. - A wrong box formula.
r / 3 + c / 3gives the same number, 1, to cell (0, 3) and cell (3, 0), which lie in different boxes. Multiplying the box row by 3 gives them 1 and 3. - An empty board. It is valid. Treating
'.'as a digit would make the second dot of every row look like a repeat. - Unsolvable but valid. Looking for a contradiction beyond repeats, such as a cell with no possible digit, is not what is asked.
- Sets that are shared. Adding the same
HashSetobject to the list nine times would make all rows share one set. Create a new set for each row, column and box.
Testing
- The statement's first board gives
true. - The same board with an 8 in the top-left corner gives
false. - Row 0 equal to
12345678.and a single 9 at row 4, column 8, with every other cell empty, givestrue. - A board with only a 5 at (0, 0) and a 5 at (0, 8) gives
false: same row. - A board with only a 5 at (0, 0) and a 5 at (8, 0) gives
false: same column. - A board with only a 5 at (0, 0) and a 5 at (1, 1) gives
false: same box. - An entirely empty board gives
true.
