37:解数独

This commit is contained in:
huangge1199@hotmail.com 2021-04-21 08:42:23 +08:00
parent 50e00e08c4
commit d52c652374
27 changed files with 1177 additions and 15 deletions

View File

@ -0,0 +1,137 @@
//编写一个程序通过填充空格来解决数独问题
//
// 数独的解法需 遵循如下规则
//
//
// 数字 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"]]
//解释输入的数独如上图所示唯一有效的解决方案如下所示
//
//
//
//
//
//
// 提示
//
//
// board.length == 9
// board[i].length == 9
// board[i][j] 是一位数字或者 '.'
// 题目数据 保证 输入数独仅有一个解
//
//
//
//
// Related Topics 哈希表 回溯算法
// 👍 819 👎 0
package leetcode.editor.cn;
import java.util.*;
//37:解数独
public class SudokuSolver {
public static void main(String[] args) {
//测试代码
Solution solution = new SudokuSolver().new Solution();
}
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public void solveSudoku(char[][] board) {
List<Integer>[][] boards = new ArrayList[9][9];
List<Integer>[] rows = new ArrayList[9];
List<Integer>[] columns = new ArrayList[9];
List<Integer>[] others = new ArrayList[9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
boards[i][j] = new ArrayList<>();
}
rows[i] = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
columns[i] = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
others[i] = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
List<List<Integer>> left = new ArrayList<>();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char ch = board[i][j];
if (ch == '.') {
left.add(Arrays.asList(i, j));
continue;
}
rows[i].remove(ch - '0');
columns[j].remove(ch - '0');
int otherIndex = (i / 3) * 3 + j / 3;
others[otherIndex].remove(ch - '0');
}
}
while (left.size() > 0) {
for (int i = 0; i < left.size(); i++) {
List<Integer> list = left.get(i);
int r = 0, c = 0, o = 0;
int x = list.get(0),y = list.get(1);
List<Integer> tempR = rows[x];
List<Integer> tempC = columns[y];
int otherIndex = (x / 3) * 3 + y / 3;
List<Integer> tempO = others[otherIndex];
while (r < tempR.size() && c < tempC.size() && o < tempO.size()) {
if(tempR.get(r).equals(tempC.get(c)) && tempC.get(c).equals(tempO.get(o))){
boards[x][y].add(tempC.get(c));
}
while (tempR.get(r)<tempC.get(c)){
tempR.remove(r);
}
while (tempC.get(c)<tempR.get(r)){
tempC.remove(c);
}
while (tempO.get(o)<tempC.get(c)){
tempO.remove(o);
}
while (tempC.get(c)<tempO.get(o)){
tempC.remove(c);
}
while (tempO.get(o)<tempR.get(r)){
tempO.remove(o);
}
while (tempR.get(r)<tempO.get(o)){
tempR.remove(r);
}
}
if(boards[x][y].size()==1){
board[x][y] = Character.forDigit(boards[x][y].get(0),10);
rows[x].remove(board[x][y] - '0');
columns[y].remove(board[x][y] - '0');
others[otherIndex].remove(board[x][y] - '0');
}
}
}
}
}
//leetcode submit region end(Prohibit modification and deletion)
}

View File

@ -0,0 +1,41 @@
<p>编写一个程序,通过填充空格来解决数独问题。</p>
<p>数独的解法需<strong> 遵循如下规则</strong></p>
<ol>
<li>数字 <code>1-9</code> 在每一行只能出现一次。</li>
<li>数字 <code>1-9</code> 在每一列只能出现一次。</li>
<li>数字 <code>1-9</code> 在每一个以粗实线分隔的 <code>3x3</code> 宫内只能出现一次。(请参考示例图)</li>
</ol>
<p>数独部分空格内已填入了数字,空白格用 <code>'.'</code> 表示。</p>
<p> </p>
<div class="top-view__1vxA">
<div class="original__bRMd">
<div>
<p><strong>示例:</strong></p>
<img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714svg.png" style="height:250px; width:250px" />
<pre>
<strong>输入:</strong>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"]]
<strong>输出:</strong>[["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"]]
<strong>解释:</strong>输入的数独如上图所示,唯一有效的解决方案如下所示:
<img src=" https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714_solutionsvg.png" style="height:250px; width:250px" />
</pre>
<p> </p>
<p><strong>提示:</strong></p>
<ul>
<li><code>board.length == 9</code></li>
<li><code>board[i].length == 9</code></li>
<li><code>board[i][j]</code> 是一位数字或者 <code>'.'</code></li>
<li>题目数据 <strong>保证</strong> 输入数独仅有一个解</li>
</ul>
</div>
</div>
</div>
<div><div>Related Topics</div><div><li>哈希表</li><li>回溯算法</li></div></div>\n<div><li>👍 819</li><li>👎 0</li></div>

View File

@ -105,21 +105,9 @@ public class ValidSudoku {
}
int num = ch - '0';
int otherIndex = (i / 3) * 3 + j / 3;
if (row[i].containsKey(num)) {
row[i].put(num, row[i].get(num) + 1);
} else {
row[i].put(num, 1);
}
if (column[j].containsKey(num)) {
column[j].put(num, column[j].get(num) + 1);
} else {
column[j].put(num, 1);
}
if (other[otherIndex].containsKey(num)) {
other[otherIndex].put(num, other[otherIndex].get(num) + 1);
} else {
other[otherIndex].put(num, 1);
}
row[i].put(num,row[i].getOrDefault(num,0) + 1);
column[j].put(num,column[j].getOrDefault(num,0) + 1);
other[otherIndex].put(num,other[otherIndex].getOrDefault(num,0) + 1);
if (row[i].get(num) > 1 || column[j].get(num) > 1 || other[otherIndex].get(num) > 1) {
return false;
}

View File

@ -0,0 +1,82 @@
### 解数独思路:
类似人的思考方式去尝试,`行``列`,还有 `3*3` 的方格内数字是 1~9 不能重复。
我们尝试填充,如果发现重复了,那么擦除重新进行新一轮的尝试,直到把整个数组填充完成。
### 算法步骤:
- 数独首先`行``列`,还有 `3*3` 的方格内数字是 1~9 不能重复。
- 声明布尔数组,表明行列中某个数字是否被使用了, 被用过视为 `true`,没用过为 `false`
- 初始化布尔数组,表明哪些数字已经被使用过了。
- 尝试去填充数组,只要`行``列` 还有 `3*3` 的方格内 出现已经被使用过的数字,我们就不填充,否则尝试填充。
- 如果填充失败,那么我们需要回溯。将原来尝试填充的地方改回来。
- 递归直到数独被填充完成。
### 代码:
代码看着多, 其实逻辑非常清楚,很容易理解。
```java [-Java]
class Solution {
public void solveSudoku(char[][] board) {
// 三个布尔数组 表明 行, 列, 还有 3*3 的方格的数字是否被使用过
boolean[][] rowUsed = new boolean[9][10];
boolean[][] colUsed = new boolean[9][10];
boolean[][][] boxUsed = new boolean[3][3][10];
// 初始化
for(int row = 0; row < board.length; row++){
for(int col = 0; col < board[0].length; col++) {
int num = board[row][col] - '0';
if(1 <= num && num <= 9){
rowUsed[row][num] = true;
colUsed[col][num] = true;
boxUsed[row/3][col/3][num] = true;
}
}
}
// 递归尝试填充数组
recusiveSolveSudoku(board, rowUsed, colUsed, boxUsed, 0, 0);
}
private boolean recusiveSolveSudoku(char[][]board, boolean[][]rowUsed, boolean[][]colUsed, boolean[][][]boxUsed, int row, int col){
// 边界校验, 如果已经填充完成, 返回true, 表示一切结束
if(col == board[0].length){
col = 0;
row++;
if(row == board.length){
return true;
}
}
// 是空则尝试填充, 否则跳过继续尝试填充下一个位置
if(board[row][col] == '.') {
// 尝试填充1~9
for(int num = 1; num <= 9; num++){
boolean canUsed = !(rowUsed[row][num] || colUsed[col][num] || boxUsed[row/3][col/3][num]);
if(canUsed){
rowUsed[row][num] = true;
colUsed[col][num] = true;
boxUsed[row/3][col/3][num] = true;
board[row][col] = (char)('0' + num);
if(recusiveSolveSudoku(board, rowUsed, colUsed, boxUsed, row, col + 1)){
return true;
}
board[row][col] = '.';
rowUsed[row][num] = false;
colUsed[col][num] = false;
boxUsed[row/3][col/3][num] = false;
}
}
} else {
return recusiveSolveSudoku(board, rowUsed, colUsed, boxUsed, row, col + 1);
}
return false;
}
}
```

View File

@ -0,0 +1,914 @@
#### 前言
我们可以考虑按照「行优先」的顺序依次枚举每一个空白格中填的数字,通过递归 + 回溯的方法枚举所有可能的填法。当递归到最后一个空白格后,如果仍然没有冲突,说明我们找到了答案;在递归的过程中,如果当前的空白格不能填下任何一个数字,那么就进行回溯。
由于每个数字在同一行、同一列、同一个九宫格中只会出现一次,因此我们可以使用 ![\textit{line}\[i\] ](./p__textit{line}_i__.png) ![\textit{column}\[j\] ](./p__textit{column}_j__.png) ![\textit{block}\[x\]\[y\] ](./p__textit{block}_x__y__.png) 分别表示第 *i* 行,第 *j* 列,第 *(x, y)* 个九宫格中填写数字的情况。在下面给出的三种方法中,我们将会介绍两种不同的表示填写数字情况的方法。
> 九宫格的范围为 ![0\leqx\leq2 ](./p__0_leq_x_leq_2_.png) 以及 ![0\leqy\leq2 ](./p__0_leq_y_leq_2_.png) 。
> 具体地,第 *i* 行第 *j* 列的格子位于第 ![(\lfloori/3\rfloor,\lfloorj/3\rfloor) ](./p___lfloor_i_3_rfloor,_lfloor_j_3_rfloor__.png) 个九宫格中,其中 ![\lflooru\rfloor ](./p__lfloor_u_rfloor_.png) 表示对 *u* 向下取整。
由于这些方法均以递归 + 回溯为基础,算法运行的时间(以及时间复杂度)很大程度取决于给定的输入数据,而我们很难找到一个非常精确的渐进紧界。因此这里只给出一个较为宽松的渐进复杂度上界 ![O(9^{9\times9}) ](./p__O_9^{9_times_9}__.png) ,即最多有 ![9\times9 ](./p__9_times_9_.png) 个空白格,每个格子可以填 *[1, 9]* 中的任意整数。
#### 方法一:递归
**思路**
最容易想到的方法是用一个数组记录每个数字是否出现。由于我们可以填写的数字范围为 *[1, 9]*,而数组的下标从 *0* 开始,因此在存储时,我们使用一个长度为 *9* 的布尔类型的数组,其中 *i* 个元素的值为 ![\text{True} ](./p__text{True}_.png) ,当且仅当数字 *i+1* 出现过。例如我们用 ![\textit{line}\[2\]\[3\]=\text{True} ](./p__textit{line}_2__3__=_text{True}_.png) 表示数字 *4* 在第 *2* 行已经出现过,那么当我们在遍历到第 *2* 行的空白格时,就不能填入数字 *4*
**算法**
我们首先对整个数独数组进行遍历,当我们遍历到第 *i* 行第 *j* 列的位置:
- 如果该位置是一个空白格,那么我们将其加入一个用来存储空白格位置的列表中,方便后续的递归操作;
- 如果该位置是一个数字 *x*,那么我们需要将 ![\textit{line}\[i\]\[x-1\] ](./p__textit{line}_i__x-1__.png) ![\textit{column}\[j\]\[x-1\] ](./p__textit{column}_j__x-1__.png) 以及 ![\textit{block}\[\lfloori/3\rfloor\]\[\lfloorj/3\rfloor\]\[x-1\] ](./p__textit{block}_lfloor_i_3_rfloor__lfloor_j_3_rfloor__x-1__.png) 均置为 ![\text{True} ](./p__text{True}_.png) 。
当我们结束了遍历过程之后,就可以开始递归枚举。当递归到第 *i* 行第 *j* 列的位置时,我们枚举填入的数字 *x*。根据题目的要求,数字 *x* 不能和当前行、列、九宫格中已经填入的数字相同,因此 ![\textit{line}\[i\]\[x-1\] ](./p__textit{line}_i__x-1__.png) ![\textit{column}\[j\]\[x-1\] ](./p__textit{column}_j__x-1__.png) 以及 ![\textit{block}\[\lfloori/3\rfloor\]\[\lfloorj/3\rfloor\]\[x-1\] ](./p__textit{block}_lfloor_i_3_rfloor__lfloor_j_3_rfloor__x-1__.png) 必须均为 ![\text{False} ](./p__text{False}_.png) 。
当我们填入了数字 *x* 之后,我们要将上述的三个值都置为 ![\text{True} ](./p__text{True}_.png) ,并且继续对下一个空白格位置进行递归。在回溯到当前递归层时,我们还要将上述的三个值重新置为 ![\text{False} ](./p__text{False}_.png) 。
**代码**
```C++ [sol1-C++]
class Solution {
private:
bool line[9][9];
bool column[9][9];
bool block[3][3][9];
bool valid;
vector<pair<int, int>> spaces;
public:
void dfs(vector<vector<char>>& board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
auto [i, j] = spaces[pos];
for (int digit = 0; digit < 9 && !valid; ++digit) {
if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) {
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false;
}
}
}
void solveSudoku(vector<vector<char>>& board) {
memset(line, false, sizeof(line));
memset(column, false, sizeof(column));
memset(block, false, sizeof(block));
valid = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.emplace_back(i, j);
}
else {
int digit = board[i][j] - '0' - 1;
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
}
}
}
dfs(board, 0);
}
};
```
```Java [sol1-Java]
class Solution {
private boolean[][] line = new boolean[9][9];
private boolean[][] column = new boolean[9][9];
private boolean[][][] block = new boolean[3][3][9];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
} else {
int digit = board[i][j] - '0' - 1;
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
for (int digit = 0; digit < 9 && !valid; ++digit) {
if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) {
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false;
}
}
}
}
```
```Python [sol1-Python3]
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def dfs(pos: int):
nonlocal valid
if pos == len(spaces):
valid = True
return
i, j = spaces[pos]
for digit in range(9):
if line[i][digit] == column[j][digit] == block[i // 3][j // 3][digit] == False:
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = True
board[i][j] = str(digit + 1)
dfs(pos + 1)
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = False
if valid:
return
line = [[False] * 9 for _ in range(9)]
column = [[False] * 9 for _ in range(9)]
block = [[[False] * 9 for _a in range(3)] for _b in range(3)]
valid = False
spaces = list()
for i in range(9):
for j in range(9):
if board[i][j] == ".":
spaces.append((i, j))
else:
digit = int(board[i][j]) - 1
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = True
dfs(0)
```
```Golang [sol1-Golang]
func solveSudoku(board [][]byte) {
var line, column [9][9]bool
var block [3][3][9]bool
var spaces [][2]int
for i, row := range board {
for j, b := range row {
if b == '.' {
spaces = append(spaces, [2]int{i, j})
} else {
digit := b - '1'
line[i][digit] = true
column[j][digit] = true
block[i/3][j/3][digit] = true
}
}
}
var dfs func(int) bool
dfs = func(pos int) bool {
if pos == len(spaces) {
return true
}
i, j := spaces[pos][0], spaces[pos][1]
for digit := byte(0); digit < 9; digit++ {
if !line[i][digit] && !column[j][digit] && !block[i/3][j/3][digit] {
line[i][digit] = true
column[j][digit] = true
block[i/3][j/3][digit] = true
board[i][j] = digit + '1'
if dfs(pos + 1) {
return true
}
line[i][digit] = false
column[j][digit] = false
block[i/3][j/3][digit] = false
}
}
return false
}
dfs(0)
}
```
```C [sol1-C]
bool line[9][9];
bool column[9][9];
bool block[3][3][9];
bool valid;
int* spaces[81];
int spacesSize;
void dfs(char** board, int pos) {
if (pos == spacesSize) {
valid = true;
return;
}
int i = spaces[pos][0], j = spaces[pos][1];
for (int digit = 0; digit < 9 && !valid; ++digit) {
if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) {
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false;
}
}
}
void solveSudoku(char** board, int boardSize, int* boardColSize) {
memset(line, false, sizeof(line));
memset(column, false, sizeof(column));
memset(block, false, sizeof(block));
valid = false;
spacesSize = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces[spacesSize] = malloc(sizeof(int) * 2);
spaces[spacesSize][0] = i;
spaces[spacesSize++][1] = j;
} else {
int digit = board[i][j] - '0' - 1;
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
}
}
}
dfs(board, 0);
}
```
#### 方法二:位运算优化
**思路与算法**
在方法一中,我们使用了长度为 *9* 的数组表示每个数字是否出现过。我们同样也可以借助位运算,仅使用一个整数表示每个数字是否出现过。
具体地,数 *b* 的二进制表示的第 *i* 位(从低到高,最低位为第 *0* 位)为 *1*,当且仅当数字 *i+1* 已经出现过。例如当 *b* 的二进制表示为 *(011000100)_2* 时,就表示数字 *3**7**8* 已经出现过。
位运算有一些基础的使用技巧。下面列举了所有在代码中使用到的技巧:
- 对于第 *i* 行第 *j* 列的位置,![\textit{line}\[i\]~|~\textit{column}\[j\]~|~\textit{block}\[\lfloori/3\rfloor\]\[\lfloorj/3\rfloor\] ](./p__textit{line}_i__~|~_textit{column}_j__~|~_textit{block}_lfloor_i_3_rfloor__lfloor_j_3_rfloor__.png) 中第 *k* 位为 *1*,表示该位置不能填入数字 *k+1*(因为已经出现过),其中 *|* 表示按位或运算。如果我们对这个值进行 ![\sim ](./p__sim_.png) 按位取反运算,那么第 *k* 位为 *1* 就表示该位置可以填入数字 *k+1*,我们就可以通过寻找 *1* 来进行枚举。由于在进行按位取反运算后,这个数的高位也全部变成了 *1*,而这是我们不应当枚举到的,因此我们需要将这个数和 ![(111111111)_2=(\text{1FF})_{16} ](./p___111111111__2_=__text{1FF}__{16}_.png) 进行按位与运算 ![\& ](./p__&_.png) ,将所有无关的位置为 *0*
- 我们可以使用按位异或运算 ![\wedge ](./p__wedge_.png) ,将第 *i* 位从 *0* 变为 *1*,或从 *1* 变为 *0*。具体地,与数 *1 << i* 进行按位异或运算即可,其中 *<<* 表示左移运算;
- 我们可以用 ![b~\&~(-b) ](./p__b_~&~__-b__.png) 得到 *b* 二进制表示中最低位的 *1*,这是因为 *(-b)* 在计算机中以补码的形式存储,它等于 ![\simb+1 ](./p__sim_b_+_1_.png) 。*b* 如果和 ![\simb ](./p__sim_b_.png) 进行按位与运算,那么会得到 *0*,但是当 ![\simb ](./p__sim_b_.png) 增加 *1* 之后,最低位的连续的 *1* 都变为 *0*,而最低位的 *0* 变为 *1*,对应到 *b* 中即为最低位的 *1*,因此当 *b* 和 ![\simb+1 ](./p__sim_b_+_1_.png) 进行按位与运算时,只有最低位的 *1* 会被保留;
- 当我们得到这个最低位的 *1* 时,我们可以通过一些语言自带的函数得到这个最低位的 *1* 究竟是第几位(即 *i* 值),具体可以参考下面的代码;
- 我们可以用 *b* 和最低位的 *1* 进行按位异或运算,就可以将其从 *b* 中去除,这样就可以枚举下一个 *1*。同样地,我们也可以用 *b**b-1* 进行按位与运算达到相同的效果,读者可以自行尝试推导。
实际上,方法二中整体的递归 + 回溯的框架与方法一是一致的。不同的仅仅是我们将一个数组「压缩」成了一个数而已。
**代码**
```C++ [sol2-C++]
class Solution {
private:
int line[9];
int column[9];
int block[3][3];
bool valid;
vector<pair<int, int>> spaces;
public:
void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
void dfs(vector<vector<char>>& board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
auto [i, j] = spaces[pos];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = __builtin_ctz(digitMask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
flip(i, j, digit);
}
}
void solveSudoku(vector<vector<char>>& board) {
memset(line, 0, sizeof(line));
memset(column, 0, sizeof(column));
memset(block, 0, sizeof(block));
valid = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.emplace_back(i, j);
}
else {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
dfs(board, 0);
}
};
```
```Java [sol2-Java]
class Solution {
private int[] line = new int[9];
private int[] column = new int[9];
private int[][] block = new int[3][3];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
} else {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask != 0 && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = Integer.bitCount(digitMask - 1);
flip(i, j, digit);
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
flip(i, j, digit);
}
}
public void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
}
```
```Python [sol2-Python3]
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def flip(i: int, j: int, digit: int):
line[i] ^= (1 << digit)
column[j] ^= (1 << digit)
block[i // 3][j // 3] ^= (1 << digit)
def dfs(pos: int):
nonlocal valid
if pos == len(spaces):
valid = True
return
i, j = spaces[pos]
mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff
while mask:
digitMask = mask & (-mask)
digit = bin(digitMask).count("0") - 1
flip(i, j, digit)
board[i][j] = str(digit + 1)
dfs(pos + 1)
flip(i, j, digit)
mask &= (mask - 1)
if valid:
return
line = [0] * 9
column = [0] * 9
block = [[0] * 3 for _ in range(3)]
valid = False
spaces = list()
for i in range(9):
for j in range(9):
if board[i][j] == ".":
spaces.append((i, j))
else:
digit = int(board[i][j]) - 1
flip(i, j, digit)
dfs(0)
```
```Golang [sol2-Golang]
func solveSudoku(board [][]byte) {
var line, column [9]int
var block [3][3]int
var spaces [][2]int
flip := func(i, j int, digit byte) {
line[i] ^= 1 << digit
column[j] ^= 1 << digit
block[i/3][j/3] ^= 1 << digit
}
for i, row := range board {
for j, b := range row {
if b == '.' {
spaces = append(spaces, [2]int{i, j})
} else {
digit := b - '1'
flip(i, j, digit)
}
}
}
var dfs func(int) bool
dfs = func(pos int) bool {
if pos == len(spaces) {
return true
}
i, j := spaces[pos][0], spaces[pos][1]
mask := 0x1ff &^ uint(line[i]|column[j]|block[i/3][j/3]) // 0x1ff 即二进制的 9 个 1
for ; mask > 0; mask &= mask - 1 { // 最右侧的 1 置为 0
digit := byte(bits.TrailingZeros(mask))
flip(i, j, digit)
board[i][j] = digit + '1'
if dfs(pos + 1) {
return true
}
flip(i, j, digit)
}
return false
}
dfs(0)
}
```
```C [sol2-C]
int line[9];
int column[9];
int block[3][3];
bool valid;
int* spaces[81];
int spacesSize;
void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
void dfs(char** board, int pos) {
if (pos == spacesSize) {
valid = true;
return;
}
int i = spaces[pos][0], j = spaces[pos][1];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = __builtin_ctz(digitMask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
flip(i, j, digit);
}
}
void solveSudoku(char** board, int boardSize, int* boardColSize) {
memset(line, 0, sizeof(line));
memset(column, 0, sizeof(column));
memset(block, 0, sizeof(block));
valid = false;
spacesSize = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces[spacesSize] = malloc(sizeof(int) * 2);
spaces[spacesSize][0] = i;
spaces[spacesSize++][1] = j;
} else {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
dfs(board, 0);
}
```
#### 方法三:枚举优化
**思路与算法**
我们可以顺着方法二的思路继续优化下去:
- 如果一个空白格只有唯一的数可以填入,也就是其对应的 *b* 值和 *b-1* 进行按位与运算后得到 *0*(即 *b* 中只有一个二进制位为 *1*)。此时,我们就可以确定这个空白格填入的数,而不用等到递归时再去处理它。
这样一来,我们可以不断地对整个数独进行遍历,将可以唯一确定的空白格全部填入对应的数。随后我们再使用与方法二相同的方法对剩余无法唯一确定的空白格进行递归 + 回溯。
**代码**
```C++ [sol3-C++]
class Solution {
private:
int line[9];
int column[9];
int block[3][3];
bool valid;
vector<pair<int, int>> spaces;
public:
void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
void dfs(vector<vector<char>>& board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
auto [i, j] = spaces[pos];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = __builtin_ctz(digitMask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
flip(i, j, digit);
}
}
void solveSudoku(vector<vector<char>>& board) {
memset(line, 0, sizeof(line));
memset(column, 0, sizeof(column));
memset(block, 0, sizeof(block));
valid = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] != '.') {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
while (true) {
int modified = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
if (!(mask & (mask - 1))) {
int digit = __builtin_ctz(mask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
modified = true;
}
}
}
}
if (!modified) {
break;
}
}
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.emplace_back(i, j);
}
}
}
dfs(board, 0);
}
};
```
```Java [sol3-Java]
class Solution {
private int[] line = new int[9];
private int[] column = new int[9];
private int[][] block = new int[3][3];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] != '.') {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
while (true) {
boolean modified = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
if ((mask & (mask - 1)) == 0) {
int digit = Integer.bitCount(mask - 1);
flip(i, j, digit);
board[i][j] = (char) (digit + '0' + 1);
modified = true;
}
}
}
}
if (!modified) {
break;
}
}
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask != 0 && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = Integer.bitCount(digitMask - 1);
flip(i, j, digit);
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
flip(i, j, digit);
}
}
public void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
}
```
```Python [sol3-Python3]
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def flip(i: int, j: int, digit: int):
line[i] ^= (1 << digit)
column[j] ^= (1 << digit)
block[i // 3][j // 3] ^= (1 << digit)
def dfs(pos: int):
nonlocal valid
if pos == len(spaces):
valid = True
return
i, j = spaces[pos]
mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff
while mask:
digitMask = mask & (-mask)
digit = bin(digitMask).count("0") - 1
flip(i, j, digit)
board[i][j] = str(digit + 1)
dfs(pos + 1)
flip(i, j, digit)
mask &= (mask - 1)
if valid:
return
line = [0] * 9
column = [0] * 9
block = [[0] * 3 for _ in range(3)]
valid = False
spaces = list()
for i in range(9):
for j in range(9):
if board[i][j] != ".":
digit = int(board[i][j]) - 1
flip(i, j, digit)
while True:
modified = False
for i in range(9):
for j in range(9):
if board[i][j] == ".":
mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff
if not (mask & (mask - 1)):
digit = bin(mask).count("0") - 1
flip(i, j, digit)
board[i][j] = str(digit + 1)
modified = True
if not modified:
break
for i in range(9):
for j in range(9):
if board[i][j] == ".":
spaces.append((i, j))
dfs(0)
```
```Golang [sol3-Golang]
func solveSudoku(board [][]byte) {
var line, column [9]int
var block [3][3]int
var spaces [][2]int
flip := func(i, j int, digit byte) {
line[i] ^= 1 << digit
column[j] ^= 1 << digit
block[i/3][j/3] ^= 1 << digit
}
for i, row := range board {
for j, b := range row {
if b != '.' {
digit := b - '1'
flip(i, j, digit)
}
}
}
for {
modified := false
for i, row := range board {
for j, b := range row {
if b != '.' {
continue
}
mask := 0x1ff &^ uint(line[i]|column[j]|block[i/3][j/3])
if mask&(mask-1) == 0 { // mask 的二进制表示仅有一个 1
digit := byte(bits.TrailingZeros(mask))
flip(i, j, digit)
board[i][j] = digit + '1'
modified = true
}
}
}
if !modified {
break
}
}
for i, row := range board {
for j, b := range row {
if b == '.' {
spaces = append(spaces, [2]int{i, j})
}
}
}
var dfs func(int) bool
dfs = func(pos int) bool {
if pos == len(spaces) {
return true
}
i, j := spaces[pos][0], spaces[pos][1]
mask := 0x1ff &^ uint(line[i]|column[j]|block[i/3][j/3]) // 0x1ff 即二进制的 9 个 1
for ; mask > 0; mask &= mask - 1 { // 最右侧的 1 置为 0
digit := byte(bits.TrailingZeros(mask))
flip(i, j, digit)
board[i][j] = digit + '1'
if dfs(pos + 1) {
return true
}
flip(i, j, digit)
}
return false
}
dfs(0)
}
```
```C [sol3-C]
int line[9];
int column[9];
int block[3][3];
bool valid;
int* spaces[81];
int spacesSize;
void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
void dfs(char** board, int pos) {
if (pos == spacesSize) {
valid = true;
return;
}
int i = spaces[pos][0], j = spaces[pos][1];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = __builtin_ctz(digitMask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
dfs(board, pos + 1);
flip(i, j, digit);
}
}
void solveSudoku(char** board, int boardSize, int* boardColSize) {
memset(line, 0, sizeof(line));
memset(column, 0, sizeof(column));
memset(block, 0, sizeof(block));
valid = false;
spacesSize = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] != '.') {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
while (true) {
int modified = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
if (!(mask & (mask - 1))) {
int digit = __builtin_ctz(mask);
flip(i, j, digit);
board[i][j] = digit + '0' + 1;
modified = true;
}
}
}
}
if (!modified) {
break;
}
}
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces[spacesSize] = malloc(sizeof(int) * 2);
spaces[spacesSize][0] = i;
spaces[spacesSize++][1] = j;
}
}
}
dfs(board, 0);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B