37. Sudoku Solver
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 9 3x3 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.
方法1:
grandyang:http://www.cnblogs.com/grandyang/p/4421852.html
思路:
使用backtracking来找valid 解,和N-Queens这道题很像。首先上来判断一些不合法的board,可以加速一点。如果合法开始dfs。这里helper为什么要用bool作为return呢?主要是我们要在遇到合法解的时候马上作出判断,比如这道题里是马上返回当前棋盘(assuming 唯一解存在)。确定了返回值之后就可以开始写dfs。首先写base case:当棋盘全部填满的时候且过程中没有发生不合法填数,可以直接返回true,以判断发现合法解,上层函数需要想办法返回当前棋盘。如果按照横向优先一步一步填数的话,这个临界条件出现在x == 9,行坐标超出边界时。否则如果当前位置为空“.”, 尝试在当前位置放入1-9其中一个数字。如果当前位置不为空,直接跳过这个位置,继续dfs下一位。尝试放入数字时按照以下做法:每次propose一个数字,都要把棋盘传入isValid这个function当中check一下是否合法,如果立刻不合法,这个propose的数字不可能产生合法解,循环至下一个数字。如果该位置放入的是合法解,这个递归会一直延续,直到最后一层返回true。这说明:1. 递归调用放入proposed数字的棋盘,判断其返回值,如果true,可以直接结束dfs,把棋盘返回主函数;2. 当所有数字都尝试一遍却没有发现合法解的时候,需要返回false。如果下一层函数无法放入任何数字,说明当前这个位置的数字也放错了,尝试下一个propose。返回false的时候一定要移除放入的值:要为之后的合法解让路。
易错点
- 用这种横向优先填入的时候,需要手动换行。并且换行要在判断base之前完成。
- 判断当前点是否需要放入propose:“.”
- 当前dfs没有找到合法解要恢复“.”
Complexity
Time complexity : O(9^m),
Space complexity : O(m), m是’.'的数目。
class Solution {
public:
void solveSudoku(vector<vector<char>>& board) {
if (board.empty() || board.size() != 9 || board[0].size() != 9) return;
solveHelper(board, 0, 0) ;
return;
}
bool solveHelper(vector<vector<char>> & board, int x, int y){
if (y >= 9) {
x ++;
y = 0;
}
if (x == 9) return true;
if (board[x][y] == '.'){
for (int k = 1; k <= 9; k++){
board[x][y] = char(k + '0');
if (isValid(board, x, y)){
if (solveHelper(board, x, y + 1))
return true;
}
board[x][y] = '.';
}
// board[x][y] = '.';
}
else {
return solveHelper(board, x, y + 1);
}
return false;
}
// i , j 是本次新放进去的数字位置,只需要检查它所在的行列小九宫格内部有没有conflict
bool isValid(vector<vector<char>> & board, int x, int y){
for (int i = 0; i < 9; i ++){
// row
if (i != x && board[i][y] == board[x][y]) return false;
// col
if (i != y && board[x][i] == board[x][y]) return false;
// cube
int r = (x / 3) * 3 + (i / 3);
int c = (y / 3) * 3 + (i % 3);
if ((r != x || c != y) && board[r][c] == board[x][y]) return false;
}
return true;
}
};
下面的解法来自YRB: https://www.cnblogs.com/yrbbest/p/4436325.html。一种非常整洁的写法。
public class Solution {
public void solveSudoku(char[][] board) {
canSolveSudoku(board);
}
private boolean canSolveSudoku(char[][] board) {
if (board == null || board.length == 0) {
return false;
}
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board.length; j++) {
if (board[i][j] == '.') {
for (char c = '1'; c <= '9'; c++) {
if (isCurrentBoardValid(board, i, j, c)) {
board[i][j] = c;
if (canSolveSudoku(board)) {
return true;
} else {
board[i][j] = '.'; // backtracking
}
}
}
return false;
}
}
}
return true;
}
private boolean isCurrentBoardValid(char[][] board, int row, int col, char c) {
for (int i = 0; i < board.length; i++) {
if (board[i][col] == c) {
return false;
}
}
for (int j = 0; j < board[0].length; j++) {
if (board[row][j] == c) {
return false;
}
}
for (int i = row / 3 * 3; i < row / 3 * 3 + 3; i++) {
for (int j = col / 3 * 3; j < col /3 * 3 + 3; j++) {
if (board[i][j] == c) {
return false;
}
}
}
return true;
}
}