37.解數獨
阿新 • • 發佈:2021-06-28
37.解數獨
題目
編寫一個程式,通過填充空格來解決數獨問題。
數獨的解法需 遵循如下規則:
數字1-9在每一行只能出現一次。
數字1-9在每一列只能出現一次。
數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。(請參考示例圖)
數獨部分空格內已填入了數字,空白格用'.'表示。
示例:
輸入: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"] ] 輸出:[ ["5","3","4","6","7","8","9","1","2"], ["6","7","2","1","9","5","3","4","8"], ["1","9","8","3","4","2","5","6","7"], ["8","5","9","7","6","1","4","2","3"], ["4","2","6","8","5","3","7","9","1"], ["7","1","3","9","2","4","8","5","6"], ["9","6","1","5","3","7","2","8","4"], ["2","8","7","4","1","9","6","3","5"], ["3","4","5","2","8","6","1","7","9"] ] 解釋:輸入的數獨如上圖所示,唯一有效的解決方案如下所示:
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/sudoku-solver
著作權歸領釦網路所有。商業轉載請聯絡官方授權,非商業轉載請註明出處。
題解
之前遞迴裡的迴圈都是一次迴圈,雖然N皇后也是NxN問題,但是每一行每一列只放一個皇后,一層for來遍歷一行,遞迴來遍歷列,然後一行一行確定皇后的位置。
數獨是每一個位置都要放一個數字,並檢查數字是否合法。
本質是列舉,那麼也可以使用回溯法。
這裡需要考慮的問題也是,如何驗證是否在一行一列一個九宮格只出現了一次?依照之前N皇后的思路,採用for迴圈的辦法。
數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。所以3x3的區間是確定的。
/* 需要知道當前數的位置來選擇比較空間 當前數的值用於比較 board用於比較物件 */ boolean isVal(int x,int y,char num,char[][] board){ for(int i=0;i<9;i++){ //一行進行比較 if(num == board[y][i]) return false; //比較列 if(num == board[i][x])return false; } /* 當x=0,1,2時,比較的是第一塊3x3 比較起點是0 當x=3,4,5時,比較的時第二塊,起點是3 */ int startX = (x/3)*3; int startY = (y/3)*3; for(int i = startY;i<startY+3;i++){ //如果是本行則不用比較了 if(i==y) continue; for(int j =startX;j<startX+3;j++){ //如果是本列也不用比較了 if(j==x) continue; if(board[i][j]==num) return false; } } return true; }
遞迴的引數和返回值
這道題是需要返回值的,找到一種解就直接返回,不需要再去找其他的解了。
引數:
char[][] board:輸入
backtracing(char[][] board);
遞迴單層邏輯
需要兩層迴圈,外層迴圈代表行數,內層迴圈代表列數。取值是需要遍歷整個空間的。
boolean backtracing(char[][] board){
for(int y=0;y<9;y++){
for(int x=0;x<9;x++){//board[y][x]代表當前數
if(board[y][x]!='.')continue;// 當前樹已有預設值,不需要再去取值了
for(char num='0';num<'10';num++){ //開始取值,範圍從0到9
if(isVal(x,y,num,board)){
board[y][x]=num; //取值
if(backtracing(board)) return true; //如果找到一種正確的情況立即返回。 這裡進行了遞迴操作
board[y][x]='.';//回溯
}
}
//9個數都取完了還不行,說明有錯,那麼就回溯往前找
return false;
}
}
//沒有false說明是正確的則返回true,這裡包含了遞迴終止條件
return true;
}
因為遞迴函式是有返回值的,所以可以利用遞迴函式的返回值來控制遞迴的結束。
程式碼
class Solution {
public void solveSudoku(char[][] board) {
backtracing(board);
}
boolean backtracing(char[][] board){
for(int y=0;y<9;y++){
for(int x=0;x<9;x++){
if(board[y][x]!='.')continue;
for(char num='1';num<='9';num++){
if(isVal(x,y,num,board)){
board[y][x]=num;
if(backtracing(board)) return true;
board[y][x]='.';
}
}
return false;
}
}
return true;
}
boolean isVal(int x,int y,char num,char[][] board){
for(int i=0;i<9;i++){
if(num == board[y][i] ||num == board[i][x]) return false;
}
int startX = (x/3)*3;
int startY = (y/3)*3;
for(int i = startY;i<startY+3;i++){
if(i==y) continue;
for(int j =startX;j<startX+3;j++){
if(j==x) continue;
if(board[i][j]==num) return false;
}
}
return true;
}
}