1. 程式人生 > 實用技巧 >Backtracking_37. 解數獨

Backtracking_37. 解數獨

編寫一個程式,通過已填充的空格來解決數獨問題。

一個數獨的解法需遵循如下規則:

數字1-9在每一行只能出現一次。
數字1-9在每一列只能出現一次。
數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。
空白格用'.'表示。

Note:

  • 給定的數獨序列只包含數字1-9和字元'.'
  • 你可以假設給定的數獨只有唯一解。
  • 給定數獨永遠是9x9形式的。

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/sudoku-solver


思路:

這是一道困難題,說實話做了很久0.0

要用到回溯的演算法,總的思想,可以暴力解決,每個格子都從1-9來填進去看看行不行,如果不行就換,行就往下不行再退回來

當然我們不這麼做,要排除掉之前就已經不能填的格子比如在【0,2】這個格子,在第0行上要排除5,3,7這三個數,橫向有了再填肯定不行

再排除縱向,有個8,再排除box範圍,5,3,6,9所以最後能填的只有1,2,4三個數先填上1,然後看後一個格子,這個時候橫向要多排除一個1,其他老樣子排除

再往後還是這樣,一直到最後一個,看看行不行,不行了就回溯,假設第一個填1一直都不行,那就換2,還不行再換4......

最後第一行找完了,那就跳到第二行,依次類推下去

class Solution {
    //定義三個陣列,分別用於記錄行使用情況,列使用情況和九個小格子使用情況
    int [][] row;
    
int [][] col; int [][] block; //定義N儲存給定陣列的長度 int N; boolean solve = false; //定義第一個方法,主要用於初始化資料 public void solveSudoku(char [][] board){ N = board.length; row = new int[N][N]; col = new int[N][N]; block = new int[N][N]; for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) { if (board[i][j] != '.'){ //將寫在board上的具體值轉換為它應該加入的座標值 int t = board[i][j] - '0' - 1; writeDown(board,i,j,t); } } } dfs(board,0,0); } private void dfs(char[][] board, int i, int j){ int box_number = (i / 3) * 3 + j / 3; //如果當前遍歷到的是.那就說明要寫入了,否則就走下一步 if (board[i][j] == '.'){ //可能填入的值有1-9,注意此處的迴圈是0-8 for (int k = 0; k < N; k++) { //判斷當前的數字是否已經用過,除非行、列、格子全部都為0否則就是重複了 if (row[i][k] + col[j][k] + block[box_number][k] != 0){ continue; } writeDown(board,i,j,k); next(board,i,j); if (!solve) clear(board,i,j,k); } }else { next(board,i ,j); } } //走下一步 private void next(char[][] board,int i,int j){ //說明已經到了終點 if (i == N - 1 && j == N - 1) solve = true; else { if (j == N - 1){ dfs(board,i + 1,0); }else { dfs(board,i,j + 1); } } } //定義一個寫入的函式 private void writeDown(char[][] board, int i, int j, int t) { //將該位置有數字的座標記錄到對應的記錄表上去,分別是對應行,對應列,對應格子 //將座標轉為表格號 int box_number = (i / 3) * 3 + j / 3; //用1表示對應位置有數字,也就是寫過了,後面遍歷的時候可以跳過這個數字 row[i][t] = 1; col[j][t] = 1; block[box_number][t] = 1; //之前把值轉換為了座標值,現在要給他賦回去 board[i][j] =(char) (t + '0' + 1); } //對應的還要有一個清除的回溯用的方法 private void clear(char[][] board, int i, int j, int t) { //將該位置有數字的座標記錄到對應的記錄表上去,分別是對應行,對應列,對應格子 //將座標轉為表格號 int box_number = (i / 3) * 3 + j / 3; //用1表示對應位置有數字,也就是寫過了,後面遍歷的時候可以跳過這個數字 row[i][t] = 0; col[j][t] = 0; block[box_number][t] = 0; board[i][j] = '.'; } }