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

37. 解數獨

技術標籤:LeetCode

37. 解數獨

題目描述

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

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

  1. 數字 1-9 在每一行只能出現一次。

  2. 數字 1-9 在每一列只能出現一次。

  3. 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

空白格用 '.' 表示。
在這裡插入圖片描述

一個數獨。
在這裡插入圖片描述
答案被標成紅色。

提示:

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

題解:

此題常規解法就是 遞迴+回溯 了。

假設我們按照 行優先 原則列舉每個空格中填的數字,然後通過 遞迴+回溯 的方法列舉所有可能的填法。

更高階的解法:跳舞鏈,求解數獨的神器,但不太會2333。

下面直接介紹常規解法 遞迴+回溯 ,直接跳過常規使用二維陣列記錄每個數字是否出現過的解法。參考 有效的數獨 中的 方法一

法一:

參考 有效的數獨 中的 方法二。

我們按照 行優先 的原則,也就是當行非法時,就找到了一組解,具體見程式碼:

法一程式碼:

class Solution {
public:
    vector<int> row, col, blo;
    bool dfs( int x, int y, vector<vector<char>>& board ) {
        if
( y < 0 ) { y = 8; if ( --x < 0 ) return true; } if ( board[x][y] == '.' ) { for ( int i = 1; i <= 9; ++i ) { if ( (row[x] >> i & 1) || (col[y] >> i & 1) || (blo[x / 3 * 3 + y / 3] >> i & 1) ) continue
; row[x] |= 1 << i; col[y] |= 1 << i; blo[x / 3 * 3 + y / 3] |= 1 << i; board[x][y] = '0' + i; if ( dfs( x, y - 1, board ) ) return true; row[x] ^= 1 << i; col[y] ^= 1 << i; blo[x / 3 * 3 + y / 3] ^= 1 << i; board[x][y] = '.'; } } else return dfs( x, y - 1, board ); return false; } void solveSudoku(vector<vector<char>>& board) { row.resize( 9, 0 ); col.resize( 9, 0 ); blo.resize( 9, 0 ); int u, k; for ( int i = 0; i < 9; ++i ) { for ( int j = 0; j < 9; ++j ) { if ( board[i][j] == '.' ) continue; u = board[i][j] & 15; row[i] |= 1 << u; col[j] |= 1 << u; k = i / 3 * 3 + j / 3; blo[k] |= 1 << u; } } dfs( 8, 8, board ); } }; /* 時間:4ms,擊敗:94.42% 記憶體:6.1MB,擊敗:99.84% */
法二:

在法一中,我們對空格一視同仁,也就是遇到空格就去填。如果一個空格可以填的數字非常多,舉個極端例子 1-9 ,那麼我們的搜尋樹會非常大。而如果一個空格只能填一個數呢,那就唯一的確定了搜尋樹的一個節點。

於是我們可以從空格能填的數字多少來考慮,優先選擇可選項少的空格來填。

我們需要整一些預處理操作:

  1. 開始時將 row、col、cell 中的每個元素初始化為 (1 << 9) - 1 ,二進位制位為 1 表示還沒用
  2. one 陣列記錄 x 二進位制表示中多少個 1
  3. map 陣列記錄數值 1 << i 第幾位為 1map[1 << i] = i

這裡,我們不在是對每個格子進行搜尋,而是選擇空格中中可選項最小的格子來遞迴處理。當所有格子都處理完後,找到解,返回即可。

詳情見程式碼:

法二程式碼:

class Solution {
public:
    vector<int> row;
    vector<int> col;
    vector<int> cell;
    vector<int> ones;
    vector<int> map;

    int lowbit( int x ) {
        return x & -x;
    }
    int getId( int x, int y ) {
        return x / 3 * 3 + y / 3;
    }
    int getVal( int x, int y ) {
        return row[x] & col[y] & cell[getId( x, y )];
    }

    bool dfs( int cnt, vector<vector<char>>& board ) {
        if ( !cnt ) return true;
        int mincnt = 10;
        int x, y;
        for ( int i = 0; i < 9; ++i ) {
            for ( int j = 0; j < 9; ++j ) {
                if ( board[i][j] != '.' ) continue;
                int t = ones[getVal(i, j)];
                if ( t < mincnt ) {
                    mincnt = t;
                    x = i, y = j;
                }
            }
        }
        for ( int i = getVal(x, y), lw; i; i -= lw ) {
            lw = lowbit(i);
            int t = map[lw];
            row[x] ^= 1 << t;
            col[y] ^= 1 << t;
            cell[getId(x, y)] ^= 1 << t;
            board[x][y] = '1' + t;
            if ( dfs ( cnt - 1, board ) ) return true;
            row[x] ^= 1 << t;
            col[y] ^= 1 << t;
            cell[getId(x, y)] ^= 1 << t;
            board[x][y] = '.';
        }
        return false;
    }

    void solveSudoku(vector<vector<char>>& board) {
        row.resize( 9, (1 << 9) - 1);
        col.resize( 9, (1 << 9) - 1 );
        cell.resize( 9, (1 << 9) - 1 );
        ones.resize( 1 << 9, 0 );
        map.resize( 1 << 9, 0 );
        for ( int i = 0; i < 9; ++i ) map[1 << i] = i;
        for ( int i = 0; i < 1 << 9; ++i ) {
            for ( int j = i; j; j -= lowbit(j) )
                ++ones[i];
        }
        int cnt = 0, val;
        for ( int i = 0; i < 9; ++i ) {
            for ( int j = 0; j < 9; ++j ) {
                if ( board[i][j] == '.' ) ++cnt;
                else {
                    val = 1 << ( board[i][j] - '1' );
                    row[i] ^= val;
                    col[j] ^= val;
                    cell[ getId(i, j) ] ^= val;
                }
            }
        }
        dfs( cnt, board );
    }
};
/*
時間:0ms,擊敗:100.00%
記憶體:6.3MB,擊敗:95.09%
*/

還有一個小技巧:把所有 空格 存起來,可以避免很多無用查詢操作。懶得去寫了。