Sudoku Validator Algorithm
Implemented in #Java
The Problem
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:
- Each row must contain the digits 1–9 without repetition.
- Each column must contain the digits 1–9 without repetition.
- 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.
Constraints:
board.length == 9
board[i].length == 9
board[i][j]
is a digit1–9
or‘.’
Example of a Valid Board
Valid Board:
The 2D array representation of the Sudoku board shown above.This input would result in an output of true, meaning that it is a valid Sudoku board.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: trueInvalid Board:
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: falseExplanation: 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.
Terminology
*The corresponding board areas highlighted in yellow.
The board consists of 9 boxes that each have 9 squares.
Note that the problem statement uses grid and board interchangeably. I will use board unless quoting the problem statement.
Board — The whole 9 x 9 Sudoku board
Box — A 3x3 Subsection of the board
Square — The atomic container that will holds the digit
Row — A contiguous section of squares along the x-axis of the board
Column — A contiguous section of squares along the y-axis of the board
The Algorithm
The algorithm needed to validate a sudoku board consists of three sub-algorithms, each solving one of the three rules given in the problem statement:
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.
In each case, the yellow square indicates the current square being processed, and the green squares indicates that that set of square is valid (no duplicates).
Rule 3 is the heart of the algorithmic challenge. The rule states that each box must be checked for duplicate digits. This is tricky because the values in the boxes don’t match the inherent structure of the arrays. Rows and columns are much easier to check because they map to the underlying data structure of arrays. Checking rows is simply the operation of iterating through each index in an array, whereas checking columns is the operation of checking the same index across all of the arrays. We will see the code for validating boxes later, for now let’s zoom out and see the programmatic solution as a whole.
The Solution
Here’s the solution as a whole for your reference. We’re going to break it down into its constituent parts, so don’t worry about understanding it yet.
Implementing Rules 1 & 2
3 // check rows/cols
4 HashSet<Character> rowSet = new HashSet<Character>();
5 HashSet<Character> colSet = new HashSet<Character>();
6 for(int i = 0; i < 9; i++) {
7 for(int j = 0; j < 9; j++) {
8 // check row char
9 char rowChar = board[i][j];
10 if(rowSet.contains(rowChar)) return false;
11 if(rowChar != ‘.’) rowSet.add(rowChar);
12
13 // check col char
14 char colChar = board[j][i];
15 if(colSet.contains(colChar)) return false;
16 if(colChar != ‘.’) colSet.add(colChar);
17 }
18 rowSet.clear();
19 colSet.clear();
20 }
The code uses a pair of nested loops to iterate through each square in the current row and column, adding the character in the square to a HashSet if the character is a digit and the corresponding HashSet doesn’t already contain it. Finding a previously seen digit causes the function to return false, signaling that the Sudoku board violates either rule 1 or 2.
The inner j loop on line 7 checks along the row and the column by swapping i and j when accessing the two dimensional array of characters that represent the board. (See lines 9 and 14). This implementation requires instantiating an additional HashSet because we need one for the current row and one for the current column. We can’t reuse the same HashSet as we’re alternating between checking the squares in the column and the row, building both HashSets in the same loop.
Finishing the inner j means that both the row and column are valid, so we clear both HashSets for reuse in the next row and column check.
Implementing Rule 3
Rule 3 is more complicated because we have to validate by boxes instead of rows or columns, which is far less intuitive than the latter. The following animation is what we need to achieve with code.
22 //check boxes
23 int rowMin = 0;
24 int rowMax = 2;
25 int colMin = 0;
26 int colMax = 2;
27
28 for(int a = 0; a < 3; a++) {
29 for(int b = 0; b < 3; b++) {
30 for(int i = rowMin; i <= rowMax; i++) {
31 for(int j = colMin; j <= colMax; j++) {
32 char c = board[i][j];
33 if(rowSet.contains(c)) return false;
34 if(c != ‘.’) rowSet.add(c);
35 }
36 }
37 rowSet.clear();
38 colMin += 3;
39 colMax += 3;
40 }
41 rowMin += 3;
42 rowMax += 3;
43 colMin = 0;
44 colMax = 2;
45 }
46 return true;
Preamble: The two outer-loops a and b are basically just counters that count from 0 to 2 inclusively for a total of 3 iterations.
a loop (process the next row of boxes):
At the end of every a loop, rowMin and rowMax are incremented by 3, and colMin and colMax are reset to 0 and 2. As this is the outermost loop, we know that these statements will be performed each time after the inner-loops have run. This translates to the algorithm moving from a row of boxes to the next row below it while simultaneously resetting the column positions so that the squares at the beginning of the row are being validated.
b loop (process the next column of boxes):
At the end of every b loop, the reused HashSet declared on line 4 named rowSet is cleared of its contents, and colMin and colMax are both incremented by 3. ColMin and colMax are responsible for moving the algorithm check horizontally and to the right along the board. The column min, max values are clamped to a range of 3; colMin will be 0, 3, and 6 while colMax will be 2, 5, and 8 in lockstep with each other. These values map to limiting the range of columns checked to the width of one box.
i loop:
i is initially set to rowMin (possible values 0, 3, or 6) with a conditional check that i is less than or equal to rowMax (possible values 2, 5, or 8). Just like how colMin and colMax clamp the algorithm to each box along the horizontal axis, rowMin and rowMax clamp the algorithm to each box along the vertical axis.
j loop:
This is the loop that does the actual validation for each square, all the previous loops have been preamble to this one. Line 32 stores the value of the square to be validated into variable c. Lines 33 and 34 are logic seen previously 10, 11 and 15, 16. The code is checking if the HashSet contains the value already, and either returns false if that condition is true, or it adds the character to the HashSet assuming it is also a digit and not a period.
All four of these loops result in the operations shown in Figure 10. The green squares represents a valid box and signifies the termination of the i loop and the code below it running (line 37)
The a loop finishes, signifying that all of the rows, columns, and boxes are valid, thus the method returns true.