Problem overview
The method isValidSudoku receives a 9 x 9 board of characters. Each cell holds a digit from '1' to '9' or '.' for an empty cell. 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 the filled cells matter. The task is to validate the digits already on the board, not to solve the puzzle, so a board can be valid even if it has no solution. In the third example, the first row holds 1 to 8 and the last cell of that row could only be a 9, but column 8 already has a 9 lower down. No digit repeats, so the answer is still true.
The second example takes the board of the first one and puts an 8 in the top left corner. That 8 clashes with the 8 in row 3 of column 0, and with the 8 in row 2 of the same box, so the answer is false.
Approach: remember what each row, column and box has seen
One way is to rescan the row, the column and the box of every filled cell. On a board this small that still works, but each cell is looked at many times. A better way is to visit every cell once and keep a record of the digits seen so far in each row, each column and each box.
That is 27 sets: nine for rows, nine for columns, nine for boxes. Every cell belongs to exactly one row, one column and one box. When a filled cell arrives, check whether its digit is already in the sets of its row, column or box. If it is, the digit appears twice and the board is invalid. If not, add the digit to the three sets and move on.
Because each set only answers "is this digit in here?", an unordered_set<char> fits: a lookup takes constant time on average.
Finding the box number
The boxes are numbered 0 to 8, left to right and then top to bottom. For the cell in row r and column c, r / 3 is the box's row (0, 1 or 2) and c / 3 is its column. The box number is (r / 3) * 3 + c / 3. For the cell at row 4, column 7, that is 1 * 3 + 2 = 5. Integer division drops the remainder, so 7 / 3 is 2, not 2.33.
The multiplication by 3 matters. Along row 0 the formula gives the box numbers 0 0 0 1 1 1 2 2 2, and down column 0 it gives 0 0 0 3 3 3 6 6 6. Adding the two quotients without it, r / 3 + c / 3, gives 2 for the cell (0, 6), 2 for (6, 0) and 2 for (3, 3), while the correct formula gives 2, 6 and 4. Different boxes would share one set and report repeats that are not there.
Step by step
- Create the row sets.
rowsis avector<unordered_set<char>>of size 9. Giving the vector a size creates nine empty sets, androws[r]will hold the digits seen in rowr. - Create
colsandboxesthe same way, nine empty sets each. - Loop over the rows with
rfrom 0 to 8. - Loop over the columns of that row with
cfrom 0 to 8. Together, the two loops visit all 81 cells. - Read the cell into a
charnameddigit. - Skip empty cells. If
digitis'.',continuewith the next cell: empty cells never break a rule. - Compute the box number with the formula above.
- Look for a repeat. If
rows[r],cols[c]orboxes[box]already counts the digit, returnfalse. The threecountchecks are joined with||. On a set,countis 1 when the value is present and 0 when it isn't. - Record the digit in
rows[r]withinsert, so later cells of the same row see it. - Record it in
cols[c]andboxes[box]too. - Return
trueafter both loops. Every filled cell was compared with all the cells visited before it in its row, column and box, and none repeated a digit.
The solution in C++
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
vector<unordered_set<char>> rows(9);
vector<unordered_set<char>> cols(9);
vector<unordered_set<char>> boxes(9);
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[r].count(digit) || cols[c].count(digit) || boxes[box].count(digit)) {
return false;
}
rows[r].insert(digit);
cols[c].insert(digit);
boxes[box].insert(digit);
}
}
return true;
}
};
Some C++ details worth knowing:
vector<unordered_set<char>> rows(9)builds nine empty sets in one go. Usingcountto test membership does not change the set. That is different fromoperator[]on amap, which inserts a default value for a missing key.- Digits stay characters. The sets hold
charvalues such as'5', not numbers, so the code never converts them. Comparing with'.'uses a character literal in single quotes; a string in double quotes would not compile against achar. - The board is passed by reference.
vector<vector<char>>& boardavoids copying all 81 cells. The code only reads from it. - Early exit.
return falseinside the nested loops leaves both loops and the method at once, so there is no need for a flag.
Complexity
Time: O(81). Each of the 81 cells is visited once and costs a few constant-time set operations. Since the board size is fixed, this is constant time. For a board of side n, the same method would take O(n²).
Space: O(81). Each filled cell adds its digit to three sets, so the sets hold at most 3 * 81 entries.
Edge cases and common mistakes
- An empty board. Nine rows of
.........are valid: nothing is filled, so nothing repeats. - A repeat in only one unit. A row such as
5.......5repeats a digit in its row, with the two 5s in different columns and different boxes. A 5 at (0, 4) and another at (8, 4) repeats in a column only. A 5 at (0, 0) and another at (2, 2) repeats in a box only. All three boards returnfalse, which is why all three kinds of set are needed. - The same digit in different units is fine. A 5 at (0, 0) and a 5 at (3, 3) share no row, column or box, so that board returns
true. - Treating
'.'as a digit. Without thecontinue, the second empty cell in a row would look like a repeated digit and a nearly empty board would come out invalid. - Solving instead of checking. The method never tries to fill a cell. A valid board may be unsolvable.
- A wrong box formula. Forgetting the multiplication, as shown above, merges different boxes into one.
Testing
- The first example board (
53..7....,6..195..., and so on) givestrue. - The same board with an 8 in the top left corner gives
false. - The board with
12345678.in row 0 and a single 9 at the end of row 4 givestrue. - Nine rows of
.........givetrue. - A board with only a 5 at (0, 0) and a 5 at (3, 3) gives
true. - A board with only a 5 at (0, 0) and a 5 at (2, 2) gives
false.
