C++實現LeetCode(37.求解數獨)

[LeetCode] 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:

  1. Each of the digits 1-9 must occur exactly once in each row.
  2. Each of the digits 1-9 must occur exactly once in each column.
  3. Each of the the digits 1-9 must occur exactly once in each of the 9 3×3 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-9and the character ’.’.
  • You may assume that the given Sudoku puzzle will have a single unique solution.
  • The given board size is always 9×9.

這道求解數獨的題是在之前那道 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 非常的相似,但更簡單一些,因為這裡隻需要檢測新加入的這個數字是否會跟其他位置引起沖突,分別檢測新加入數字的行列和所在的小區間內是否有重復的數字即可,參見代碼如下:

解法一:

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 循環,用來同時檢測行列和小區間是否有沖突,註意正確的坐標轉換即可,參見代碼如下:

解法二:

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

到此這篇關於C++實現LeetCode(37.求解數獨)的文章就介紹到這瞭,更多相關C++實現求解數獨內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: