一.题目描述
NC47数独
题目链接:
https://www.nowcoder.com/practice/5e6c424b82224b85b64f28fd85761280?tpId=196&&tqId=37077&rp=1&ru=/activity/oj&qru=/ta/job-code-total/question-ranking
请编写一个程序,给数独中的剩余的空格填写上数字,空格用字符'.'表示,假设给定的数独只有唯一的解法。
二.算法(搜索)
思考:利用暴力搜索对每一种情况进行合法性判断,可以设置三个搜索变量dfs(board,x,y):
(1)(x,y)表示搜索坐标,那么每一个小数组中任意一个坐标可以表示为:
1.横坐标-(x/3)3+i/3
2.纵坐标-(y/3)3+i%3
(2)board表示要被搜索的数独二维数组
思路:经典的搜索问题,和八皇后问题类似,关键实现一个合法性判断函数,isValid(x,y,c)即在(x,y)位置放入c字符是否可行 ,即行不能重复 ,列不能重复,九宫格不能重复dfs(x,y,c) 当遇到存在字符时跳过。空格时,填入isValid可行的字符,并跳转到下一个dfs(board,x,y+1),当index==81即所有字符填充完毕时,是最终结束搜索,迅速返回,可采取标记变量方式。
下面是代码:
#include <vector> using namespace std; class Solution { public: void solveSudoku(vector<vector<char> > &board) { //从(0,0)位置开始搜索 dfs(board, 0, 0); } bool dfs(vector<vector<char> > &board, int x, int y) { if (x==9) return true; if (y==9) return dfs(board,x+1,0);//表示该列已经检验完了 对下一行进行检验 //如果被数字占位置了 直接进入下一列 if (board[x][y] != '.') return dfs(board, x, y+1); //遍历1-9进行暴力判断 填入是否合法 for (char c='1';c<='9';c++) { //利用isValid对填入字符进行判断是否合法 if (!isValid(board, x, y, c)) continue; //回溯操作 board[x][y] = c; if (dfs(board,x,y+1)) return true; board[x][y] = '.'; } return false; } bool isValid(vector<vector<char> > &board, int x, int y, char ch) { for (int i = 0; i < 9; ++i) { //分别从三个方面进行检验 //所在列是不是有相同数字 if (board[x][i] == ch) return false; //所在行是不是有相同数字 if (board[i][y] == ch) return false; //所在的小矩形是不是有相同数字 if (board[(x/3)*3 + i/3][(y/3)*3 + i%3] == ch) return false; } return true; } };
时间复杂度:o(n*n)其中n为数独的横纵大小
空间复杂度:o(1) 不需要开辟额外空间
优缺点:思考过程和实现简单,但是时间复杂度比较高
三.算法(位运算优化)
在算法二中,我们使用了暴力检验每一个状态是不是合法。我们同样也可以借助位运算,仅使用一个整数表示每个数字是否出现过。
具体地,数b的二进制表示的第i位(从低到高,最低位为第0位)为 11,当且仅当数字i+1已经出现过。例如当b的二进制表示为 (011000100)2时候,就表示3,7,8已经出现过。
下面直接来看代码:
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]; //0x1ff 表示全为1 表全部填入了数字 int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff; for (; mask && !valid; mask &= (mask - 1)) { int digitMask = mask & (-mask); //找到最低位的非0位置 int digit = __builtin_ctz(digitMask); //内置函数__builtin_ctz:返回前导的0的个数。 //回溯 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 进行填入 spaces.emplace_back(i, j); //emplace_back省去了移动构造函数 效率高 } else { int digit = board[i][j] - '0' - 1; flip(i,j,digit); } } } dfs(board, 0); } };
时间复杂度:o(1)
空间复杂度:o(n * n)需要开辟额外的空间
优缺点:位运算优化难实现,但是时间效率很高