37. 解數獨
阿新 • • 發佈:2021-02-01
技術標籤:LeetCode
37. 解數獨
題目描述
編寫一個程式,通過填充空格來解決數獨問題。
一個數獨的解法需遵循如下規則:
-
數字
1-9
在每一行只能出現一次。 -
數字
1-9
在每一列只能出現一次。 -
數字
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
,那麼我們的搜尋樹會非常大。而如果一個空格只能填一個數呢,那就唯一的確定了搜尋樹的一個節點。
於是我們可以從空格能填的數字多少來考慮,優先選擇可選項少的空格來填。
我們需要整一些預處理操作:
- 開始時將
row、col、cell
中的每個元素初始化為(1 << 9) - 1
,二進位制位為1
表示還沒用 one
陣列記錄x
二進位制表示中多少個1
map
陣列記錄數值1 << i
第幾位為1
,map[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%
*/
還有一個小技巧:把所有 空格 存起來,可以避免很多無用查詢操作。懶得去寫了。