Sudoku Solver, a hard leetcode problem (backtracking)
Problem is here: https://leetcode.com/problems/sudoku-solver/description/
A sudoku puzzle...
...and its solution numbers marked in red.
The idea is to have 3 sets of hash tables keeping track of which numbers have already been used for each row (9 hash tables), column (9 hash tables) and quadrants (9 hash tables).
The backtracking will be controlled by an index from 0..81, and with this index you should be able to access the row, the column and the quadrant. The formula for the quadrant based on the index took me the longest to figure out, but with some paper/pen/trial/error, in few minutes you can find it (actual sketch below):
First add all the current numbers to the appropriate hash tables. Then start the backtracking: if you reach the end (index == 81), you have found the solution. If the current cell contains an initial number, skip it. Otherwise, backtrack it for all numbers 1..9. Code is below, cheers, Marcelo.
Write a program to solve a Sudoku puzzle by filling the empty cells.
A sudoku solution must satisfy all of the following rules:
- Each of the digits
1-9
must occur exactly once in each row. - Each of the digits
1-9
must occur exactly once in each column. - Each of the the digits
1-9
must occur exactly once in each of the 93x3
sub-boxes of the grid.
Empty cells are indicated by the character
'.'
.A sudoku puzzle...
...and its solution numbers marked in red.
Note:
- The given board contain only digits
1-9
and the character'.'
. - You may assume that the given Sudoku puzzle will have a single unique solution.
- The given board size is always
9x9
.
The idea is to have 3 sets of hash tables keeping track of which numbers have already been used for each row (9 hash tables), column (9 hash tables) and quadrants (9 hash tables).
The backtracking will be controlled by an index from 0..81, and with this index you should be able to access the row, the column and the quadrant. The formula for the quadrant based on the index took me the longest to figure out, but with some paper/pen/trial/error, in few minutes you can find it (actual sketch below):
First add all the current numbers to the appropriate hash tables. Then start the backtracking: if you reach the end (index == 81), you have found the solution. If the current cell contains an initial number, skip it. Otherwise, backtrack it for all numbers 1..9. Code is below, cheers, Marcelo.
public class Solution { public void SolveSudoku(char[][] board) { Hashtable[] rows = new Hashtable[9]; Hashtable[] cols = new Hashtable[9]; Hashtable[] quadrants = new Hashtable[9]; for (int i = 0; i < 9; i++) { rows[i] = new Hashtable(); cols[i] = new Hashtable(); quadrants[i] = new Hashtable(); } //Add all current values to the hash tables int index = 0; for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) { if (board[r][c] != '.') { AddNumber(index, (int)(board[r][c] - '0'), rows, cols, quadrants); } index++; } } SolveSudoku(board, 0, rows, cols, quadrants); } private bool SolveSudoku(char[][] board, int index, Hashtable[] rows, Hashtable[] cols, Hashtable[] quadrants) { if (index == 81) return true; int r = index / 9; int c = index % 9; if (board[r][c] != '.') return SolveSudoku(board, index + 1, rows, cols, quadrants); else { for (int n = 1; n <= 9; n++) { if (AddNumber(index, n, rows, cols, quadrants)) { board[r][c] = (char)(n + '0'); if (SolveSudoku(board, index + 1, rows, cols, quadrants)) return true; board[r][c] = '.'; RemoveNumber(index, n, rows, cols, quadrants); } } } return false; } private bool AddNumber(int index, int number, Hashtable[] rows, Hashtable[] cols, Hashtable[] quadrants) { int r = index / 9; int c = index % 9; int q = (index / 27) * 3 + (index % 9) / 3; if (!rows[r].ContainsKey(number) && !cols[c].ContainsKey(number) && !quadrants[q].ContainsKey(number)) { rows[r].Add(number, true); cols[c].Add(number, true); quadrants[q].Add(number, true); return true; } return false; } private void RemoveNumber(int index, int number, Hashtable[] rows, Hashtable[] cols, Hashtable[] quadrants) { int r = index / 9; int c = index % 9; int q = (index / 27) * 3 + (index % 9) / 3; rows[r].Remove(number); cols[c].Remove(number); quadrants[q].Remove(number); } }
Thank you for sharing.
ReplyDeleteI really like the ideas in your implementation. I enumerate one by one in the following:
1. Preprocess all rows and columns and quadrants into data structure, O(1) to look up if the digit is available for next empty slot;
2. index variable from 0 to 80 is used to track the progress of depth first search, help to identify base case;
3. Step 1 is also very efficient since every element with digit values in the board will be added once at the beginning.
I make some minor changes and add some comment to help follow the calculation.
Hashtable -> HashSet
variable names:
rows -> rowDigits
cols -> colDigits
I rewrote the code and practice one more time, also I shared on Leetcode discuss.
https://leetcode.com/problems/sudoku-solver/discuss/217272/C-One-more-elegant-solution
This comment has been removed by the author.
ReplyDeleteOne more comment, I took some time to figure out the calculation and add some comment to help people to read the code of function AddNumber(...)
ReplyDelete///
/// The row and column should not include same digit more than once; quadrant as well.
///
private static bool AddNumber(int index,
int number,
HashSet[] rows,
HashSet[] cols,
HashSet[] quadrants)
{
// one row has 9 columns
int r = index / 9;
int c = index % 9;
// there are three rows and three columns quadrants.
// each row will have 27 elements
// each quadrant will have 9 elements
// First row quadrant
// Quadrant1 | Quadrant2 | Quadrant3
// by index
// 0 - 8 | 9 - 17 | 18 - 26
// by column
// 0 1 2 | 3 4 5 | 6 7 8
int q = (index / 27) * 3 + (index % 9) / 3;
if (!rows[r].Contains(number) &&
!cols[c].Contains(number) &&
!quadrants[q].Contains(number))
{
rows[r].Add(number);
cols[c].Add(number);
quadrants[q].Add(number);
return true;
}
return false;
}
Very nice!!! This problem reminds me of the 8-queens problem doesn’t it? Same ideas. Cheers, Marcelo
ReplyDeleteLoved this problem! There are actually a lot of tricks that can be used to prune the search space to speed up the solution space like considering the most constrained positions first, but since I'm lazy I went ahead with a brute-force in Python:
ReplyDeleteclass Solution:
VALID_VALUES = set(map(str, range(1, 10)))
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: void Do not return anything, modify board in-place instead.
"""
def rows(row):
return board[row]
def columns(column):
for row in range(len(board)):
yield board[row][column]
def squares(row, column):
start_row = (row // 3) * 3
start_column = (column // 3) * 3
for row in range(start_row, start_row+3):
for column in range(start_column, start_column+3):
yield board[row][column]
def is_set(value):
return value != '.'
def safe(row, column):
return self.VALID_VALUES - (set(rows(row)) | set(columns(column)) | set(squares(row, column)))
def unset():
for row in range(len(board)):
for column in range(len(board[0])):
if not is_set(board[row][column]):
yield row, column
def solve(pos_idx, positions):
row, column = positions[pos_idx]
for safe_value in safe(row, column):
board[row][column] = safe_value
if pos_idx == len(positions) - 1:
return True
solution = solve(pos_idx+1, positions)
if solution:
return True
board[row][column] = '.'
unset = list(unset())
if not unset:
return # already solved
solve(0, unset)
https://gist.github.com/ttsugriy/1f93921fa8fb46783d4d144f6c4b873b
Thanks for sharing!
I love sudokuonlineplay.net
ReplyDelete