判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
示例 1:
输入:
[
[“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”]
]
输出: true
示例 2:
输入:
[
[“8”,“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”]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。
初步思路:
这个题的思路还是挺明确的,不管是采用暴力的方式还是什么方式,无非就是三个判断,我遍历数组,只要保证每一行没有重复的,每一列没有重复的,每一个九宫格没有重复的,那么这个就是有效数独。
所以,我得第一次解法是
这个题初步想法写三个判断法则
1. 遍历行
每一行, 建一个map用来统计数的个数,如果不是1, 返回false
2. 遍历列
每一列,和行的思路一样
3. 遍历每个九宫格
九宫格的遍历有点复杂,初步想法就是把九宫格中心的坐标先以键值对的形式存入数组,然后遍历数组,对每一个九宫格,建一个map统计当前九宫格里面出现的数字数量,如果大于1,返回false。 即用九宫格中心的那个点代表一个九宫格。
所以就根据上述的思想完成了第一个版本的代码如下:
class Solution
{
public:
bool isValidSudoku(vector<vector<char> >& board)
{
int rowsize = board.size();
int colsize = board[0].size();
// 遍历每一行,不能有重复的
for (int i=0; i<rowsize; i++)
{
map<char, int> m;
map<char, int> ::iterator it;
for (int j=0; j<colsize; j++)
{
if (board[i][j] != '.')
{
m[board[i][j]]++;
}
}
for (it=m.begin(); it!=m.end(); it++)
{
if (it->second >1)
return false;
}
}
// 遍历每一列,不能有重复的
for (int i=0; i<colsize; i++)
{
map<char, int>m;
map<char, int> ::iterator it;
for (int j=0; j<rowsize; j++)
{
if (board[j][i] != '.')
{
m[board[j][i]] ++;
}
}
for (it=m.begin(); it!=m.end(); it++)
{
if (it->second >1)
return false;
}
}
// 遍历每个九宫格
// 先建立一个二维数组,把每个九宫格的中心坐标找好
int loc[] = {1, 1,
1, 4,
1, 7,
4, 1,
4, 4,
4, 7,
7, 1,
7, 4,
7, 7
};
for (int i=0; i<17; i+=2)
{
// 获取九空格的中心坐标
int locrownum = loc[i];
int loccolnum = loc[i+1];
// 每一个九空格,判断是否有重复的数字
map<char, int> m;
if( board[locrownum-1][loccolnum-1] != '.')
{
m[board[locrownum-1][loccolnum-1]]++;
}
if(board[locrownum-1][loccolnum] != '.')
{
m[board[locrownum-1][loccolnum]]++;
}
if(board[locrownum-1][loccolnum+1] != '.')
{
m[board[locrownum-1][loccolnum+1]]++;
}
if(board[locrownum][loccolnum-1] != '.')
{
m[board[locrownum][loccolnum-1]]++;
}
if(board[locrownum][loccolnum] != '.')
{
m[board[locrownum][loccolnum]]++;
}
if(board[locrownum][loccolnum+1] != '.')
{
m[board[locrownum][loccolnum+1]]++;
}
if(board[locrownum+1][loccolnum-1] != '.')
{
m[board[locrownum+1][loccolnum-1]]++;
}
if(board[locrownum+1][loccolnum] != '.')
{
m[board[locrownum+1][loccolnum]]++;
}
if(board[locrownum+1][loccolnum+1] != '.')
{
m[board[locrownum+1][loccolnum+1]]++;
}
// 弄一个迭代器,进行有效区分
map<char, int> ::iterator it;
for (it=m.begin(); it!=m.end(); it++)
{
if (it->second >1)
return false;
}
}
return true;
}
};
这个代码A掉这个题目没有任何问题,但是太繁琐了,尤其是九宫格那竟然能写九遍判断也是没谁了,完全是暴力思想了。 所以看了看人家的题解,对这个题做了一个优化,没想到两次循环遍历一遍就可以看出是不是有效数独。
改进思路:
思想依然是同样的思想,但是在效率上有了提高,就是我用一次遍历所有的元素就可以判断出是不是有效数独。 这里的关键是弄清楚每一个子数独和整个数独的坐标关系:借助官方的图来理解:
看上面这个图,99的一个大数独是由9个33的小数独组成的,编号0-8,那么遍历大数独的时候,i, j与每一个小数独的编号的关系是
box_index = (i / 3) * 3 + j / 3
所以基于这个关系式,就可以写思路了:
首先,我要见三个9行10列的二维数组,每一个统计行,列,子宫格里面元素的出现的次数
然后,遍历这个大数独, 对于出现的每一个元素,如果不是".",那么相应的统计个数的数组相应的位置就进行加1, 如果发现重复了,返回false。
思路看着还是有点晕乎,看代码吧。
class Solution
{
public:
bool isValidSudoku(vector<vector<char> >& board)
{
// 建立三个9行10列(0-9)的二维数组统计个数
vector<vector<int> > rows(9, vector<int>(10, 0)); // 9行的每一行是否有重复的
vector<vector<int> > cols(9, vector<int>(10, 0)); // 9列的每一列里面是否有重复的
vector<vector<int> > cell(9, vector<int>(10, 0)); // 9个子宫格的每一个子宫格里面有没有重复的(注意,这里得经过那个i,j的映射)
for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 9; ++j)
{
if(board[i][j] != '.')
{
int e = board[i][j] - '0';
// 判断行中是否有重复的
if (rows[i][e] == 0)
rows[i][e]++;
else
return false;
// 判断列中是否有重复的
if (cols[j][e] == 0)
cols[j][e]++;
else
return false;
// 判断小的九宫格里面是否有重复的
int ci = map(i, j);
if (cell[ci][e] == 0)
cell[ci][e]++;
else
return false;
}
}
}
return true;
}
int map(int i, int j)
{
int a = i / 3;
int b = j / 3;
return a*3 + b;
}
};
小总一下:
这个题总体上思路比较清晰,开门见山,但是操作起来,如果不知道那个子宫格和整体的数独之间的那个对应关系的话,很难想到后面的改进思路这一个,通过一次遍历就判断出来,所以还是挺佩服我第一种解法的,这次以解决问题为根本目标了,好与坏,先解决出来再说。其次就是学到了vector创建二维数组并且使用的技巧,并且通过资料整理了一下:
https://blog.csdn.net/wuzhongqiang/article/details/103213734
PS: 这个题看过题解之后,发现还有一种位运算的解法,看了很长时间没怎么看明白,就先不整理了,可以参考一下:
https://leetcode-cn.com/problems/valid-sudoku/solution/java-wei-yun-suan-xiang-jie-miao-dong-zuo-biao-bia/