Write a program to solve a Sudoku puzzle by filling the empty cells.html
A sudoku solution must satisfy all of the following rules:git
1-9
must occur exactly once in each row.1-9
must occur exactly once in each column.1-9
must occur exactly once in each of the 9 3x3
sub-boxes of the grid.Empty cells are indicated by the character '.'
.github
A sudoku puzzle...數組
...and its solution numbers marked in red.函數
Note:post
1-9
and the character '.'
.9x9
.
這道求解數獨的題是在以前那道 Valid Sudoku 的基礎上的延伸,以前那道題讓咱們驗證給定的數組是否爲數獨數組,這道讓求解數獨數組,跟此題相似的有 Permutations,Combinations, N-Queens 等等,其中尤爲是跟 N-Queens 的解題思路及其類似,對於每一個須要填數字的格子帶入1到9,每代入一個數字都斷定其是否合法,若是合法就繼續下一次遞歸,結束時把數字設回 '.',判斷新加入的數字是否合法時,只須要斷定當前數字是否合法,不須要斷定這個數組是否爲數獨數組,由於以前加進的數字都是合法的,這樣可使程序更加高效一些,總體思路是這樣的,可是實現起來能夠有不一樣的形式。一種實現形式是遞歸帶上橫縱座標,因爲是一行一行的填數字,且是從0行開始的,因此當i到達9的時候,說明全部的數字都成功的填入了,直接返回 ture。當j大於等於9時,當前行填完了,須要換到下一行繼續填,則繼續調用遞歸函數,橫座標帶入 i+1。不然看若當前數字不爲點,說明當前位置不須要填數字,則對右邊的位置調用遞歸。若當前位置須要填數字,則應該嘗試填入1到9內的全部數字,讓c從1遍歷到9,每當試着填入一個數字,都須要檢驗是否有衝突,使用另外一個子函數 isValid 來檢驗是否合法,假如不合法,則跳過當前數字。若合法,則將當前位置賦值爲這個數字,並對右邊位置調用遞歸,若遞歸函數返回 true,則說明能夠成功填充,直接返回 true。不行的話,須要重置狀態,將當前位置恢復爲點。若全部數字都嘗試了,仍是不行,則最終返回 false。檢測當前數組是否合法的原理跟以前那道 Valid Sudoku 很是的類似,但更簡單一些,由於這裏只須要檢測新加入的這個數字是否會跟其餘位置引發衝突,分別檢測新加入數字的行列和所在的小區間內是否有重複的數字便可,參見代碼以下:url
解法一:spa
class Solution { public: void solveSudoku(vector<vector<char>>& board) { helper(board, 0, 0); } bool helper(vector<vector<char>>& board, int i, int j) { if (i == 9) return true; if (j >= 9) return helper(board, i + 1, 0); if (board[i][j] != '.') return helper(board, i, j + 1); for (char c = '1'; c <= '9'; ++c) { if (!isValid(board, i , j, c)) continue; board[i][j] = c; if (helper(board, i, j + 1)) return true; board[i][j] = '.'; } return false; } bool isValid(vector<vector<char>>& board, int i, int j, char val) { for (int x = 0; x < 9; ++x) { if (board[x][j] == val) return false; } for (int y = 0; y < 9; ++y) { if (board[i][y] == val) return false; } int row = i - i % 3, col = j - j % 3; for (int x = 0; x < 3; ++x) { for (int y = 0; y < 3; ++y) { if (board[x + row][y + col] == val) return false; } } return true; } };
還有另外一種遞歸的寫法,這裏就不帶橫縱座標參數進去,因爲遞歸須要有 boolean 型的返回值,因此不能使用原函數。由於沒有橫縱座標,因此每次遍歷都須要從開頭0的位置開始,這樣無形中就有了大量的重複檢測,致使這種解法雖然寫法簡潔一些,但擊敗率是沒有上面的解法高的。這裏的檢測數組衝突的子函數寫法也比上面簡潔很多,只用了一個 for 循環,用來同時檢測行列和小區間是否有衝突,注意正確的座標轉換便可,參見代碼以下:code
解法二:htm
class Solution { public: void solveSudoku(vector<vector<char>>& board) { helper(board); } bool helper(vector<vector<char>>& board) { for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] != '.') continue; for (char c = '1'; c <= '9'; ++c) { if (!isValid(board, i, j, c)) continue; board[i][j] = c; if (helper(board)) return true; board[i][j] = '.'; } return false; } } return true; } bool isValid(vector<vector<char>>& board, int i, int j, char val) { for (int k = 0; k < 9; ++k) { if (board[k][j] != '.' && board[k][j] == val) return false; if (board[i][k] != '.' && board[i][k] == val) return false; int row = i / 3 * 3 + k / 3, col = j / 3 * 3 + k % 3; if (board[row][col] != '.' && board[row][col] == val) return false; } return true; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/37
相似題目:
Unique Paths III
參考資料:
https://leetcode.com/problems/sudoku-solver/
https://leetcode.com/problems/sudoku-solver/discuss/15853/Simple-and-Clean-Solution-C%2B%2B