1. 程式人生 > 其它 >37.解數獨

37.解數獨

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;
        }

}