题目:
Write a program to solve a Sudoku puzzle by filling the empty cells.
Empty cells are indicated by the character '.'.
You may assume that there will be only one unique solution.
初步思路:
首先找出'.'的位置放入vector<int> unknown中,然后填入数字,看看
是否有效,如果没有有效数字则返回上一个unknow位置。
代码如下
一定要注意边界的处理!!!!!!!!!!!!!
class Solution {
public:
bool isXYvalid(int u, vector<vector<char>> board)
{
vector<bool> row(9, false), col(9, false), block(9, false);
int x = u / 9, y = u % 9;
//首先检查所在行
for (int i = 0; i < 9; i++)
{
if (board[x][i] != '.'){
if (row[board[x][i] - '0' - 1] == true)//易错点 一定要减1
return false;
row[board[x][i] - '0' - 1] = true;
}
if (board[i][y] != '.'){
if (col[board[i][y] - '0' - 1] == true)
return false;
col[board[i][y] - '0' - 1] = true;
}
int bx = (x / 3) * 3 + i / 3, by = (y / 3) * 3 + i % 3;
if (board[bx][by] != '.'){
if (block[board[bx][by] - '0' - 1] == true)
return false;
block[board[bx][by] - '0' - 1] = true;
}
}
return true;
}
void tryFill(int start, vector<int> unknown, vector<vector<char>>& board)
{
if (start < 0) return;
int i = start;
for (; i < unknown.size(); i++)
{
int u = unknown[i];
int x = u / 9, y = u % 9;
int f;
if (board[x][y] == '.')
f = 1;
else
f = board[x][y] - '0' + 1;
for (; f < 10; f++)
{
board[x][y] = '0' + f;
if (isXYvalid(u, board))
break;
}
if (f == 10){
board[x][y] = '.';
tryFill(i - 1, unknown, board);
break;//太重要了 这个break
}
}
}
void solveSudoku(vector<vector<char>>& board) {
//先用hash 去看每个cell可能的数字
//再回溯法
//bool hrow[9][9] = {false}, hcol[9][9] = {false}, hblock[9][9] = {false};
vector<int> unknown;//x = u / 9 , y = u % 9 u->[0, 80];
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (board[i][j] == '.')
unknown.push_back(i * 9 + j);
tryFill(0, unknown, board);
}
};
提交后竟要用1865msAC,看来上面的做法很low。
思考如何改进:
我们发现上面的做法是每个位置从1到9逐个尝试并且check。每次回溯也要用这么多步,每次check的复杂度都在27以上(见isXYvalid 函数)。
改进:每个位置只有几个数字合适,并不用1到9尝试一遍,如下代码
class Solution {
public:
vector<int> validNum(int u, vector<vector<char>> board)
{
vector<int> ans;
vector<bool> used(9, false);
int x = u / 9, y = u % 9;
for (int i = 0; i < 9; i++)
{
if (board[x][i] != '.'){
used[board[x][i] - '0' - 1] = true;
}
if (board[i][y] != '.'){
used[board[i][y] - '0' - 1] = true;
}
int bx = (x / 3) * 3 + i / 3, by = (y / 3) * 3 + i % 3;
if (board[bx][by] != '.'){
used[board[bx][by] - '0' - 1] = true;
}
}
if (board[x][y] != '.') used[board[x][y] - '0' - 1] = false;
for (int i = 0; i < 9; i++)
{
if (!used[i]) ans.push_back(i + 1);// 不要忘记+1
}
return ans;
}
void tryFill(int start, vector<int> unknown, vector<vector<char>>& board)
{if(start > 50)
cout<<start<<" ";
if (start < 0) return;
int i = start;
for (; i < unknown.size(); i++)
{
int u = unknown[i];
int x = u / 9, y = u % 9;
vector<int> validN = validNum(u, board);
int validLen = validN.size();
int index;
if (board[x][y] == '.')
index = 0;
else
for (int i = 0; i < 9; i++)
if (validN[i] == board[x][y] - '0')
{
index = i + 1;// 不要忘记+1
break;
}
for (; index < validLen; index++)
{
board[x][y] = validN[index] + '0';
if (i != unknown.size() - 1){
vector<int> NextValidN = validNum(unknown[i + 1], board);
if (NextValidN.size() != 0)
break;
}
}
if (index == validLen && i != unknown.size() - 1 )//这里一定要有i != unknown.size() - 1, 因为当填入最后一个空格,这个一定是有效的
{
board[x][y] = '.';
tryFill(i - 1, unknown, board);
break;//太重要了 这个break
}
}
}
void solveSudoku(vector<vector<char>>& board) {
//先用hash 去看每个cell可能的数字
//再回溯法
//bool hrow[9][9] = {false}, hcol[9][9] = {false}, hblock[9][9] = {false};
vector<int> unknown;//x = u / 9 , y = u % 9 u->[0, 80];
for (int i = 0; i < 9; i++){
for (int j = 0; j < 9; j++)
if (board[i][j] == '.')
unknown.push_back(i * 9 + j);
}
tryFill(0, unknown, board);
cout << "";
}
};
代码提交489msAC,还是慢。
慢在什么地方呢?个人猜想是对validNum函数的不断调用,可不可以省去这个函数?
解决方法:用hash table的办法。定义bool usedRow[9][10];bool usedCol[9][10];bool usedBlock[9][10];用来存储数字是否出现过,
最后直接回溯法解决。
class Solution {
public:
bool usedRow[9][10];
bool usedCol[9][10];
bool usedBlock[9][10];
bool tryFill(int cur, vector<int> unknown, vector<vector<char>>& board)
{
cout << cur << " ";
if (cur == unknown.size())return true;
int u = unknown[cur];
int x = u / 9, y = u % 9;
for (int attemp = 1; attemp < 10; attemp++)
{
if (usedRow[x][attemp] == false && usedCol[y][attemp] == false && usedBlock[x / 3 * 3 + y / 3][attemp] == false)
{
board[x][y] = attemp + '0';
usedRow[x][attemp] = true;
usedCol[y][attemp] = true;
usedBlock[x / 3 * 3 + y / 3][attemp] = true;
if (tryFill(cur + 1, unknown, board))
return true;
board[x][y] = '.';
usedRow[x][attemp] = false;
usedCol[y][attemp] = false;
usedBlock[x / 3 * 3 + y / 3][attemp] = false;
}
}
return false;
}
void solveSudoku(vector<vector<char>>& board) {
memset(usedRow, false, sizeof(bool) * 90);
memset(usedCol, false, sizeof(bool) * 90);
memset(usedBlock, false, sizeof(bool) * 90);
vector<int> unknown;//x = u / 9 , y = u % 9 u->[0, 80];
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (board[i][j] == '.')
unknown.push_back(i * 9 + j);
else
{
int num = board[i][j] - '0';
usedRow[i][num] = true;
usedCol[j][num] = true;
usedBlock[i / 3 * 3 + j / 3][num] = true;
}
tryFill(0, unknown, board);
}
};
提交69msAC还可以,性能有很大幅度提高。
总结:
首先思路要清晰!
边界情况一定要好好设计,比如+1, -1的情况。
对于回溯,递归一定一定要注意边界的条件,比如什么时候return,什么时候break。