本文介绍如何验证数独的有效性及实现矩阵置零的算法。针对数独验证,提出了遍历行、列和宫格的方法来确保每行、每列及每个宫格内的数字1-9不重复。对于矩阵置零问题,则采用标记法实现,通过两次遍历分别标记含有0的行和列,最后将这些行和列置零。

本文最初发布于 CSDN,现迁移至本站并做格式整理。内容保留原始观点与发布时间。

一、有效的数独

1、题目:

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。*

注意:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。

2、代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
bool isValidSudoku(char** board, int boardSize, int* boardColSize){
int check[10];
memset(check,0,sizeof(int)*10);

//行遍历
for(int i=0;i<boardSize;i++){
memset(check,0,sizeof(int)*10);
for(int j=0;j<*boardColSize;j++){
if(board[i][j] != '.'){
int t = (int)(board[i][j] - '0');
if(check[t]){
return false;
}
check[t]++;
}
}
}

//列遍历
for(int i=0;i<*boardColSize;i++){
memset(check,0,sizeof(int)*10);
for(int j=0;j<boardSize;j++){
if(board[j][i] != '.'){
int t = (int)(board[j][i] - '0');
if(check[t]){
return false;
}
check[t]++;
}
}
}

//宫格遍历
for(int i=0;i<9;i++){
int xi_Sudoku = (i % 3) * 3;
int yi_Sudoku = (i / 3) * 3;
memset(check,0,sizeof(int)*10);
for(int m=0;m<3;m++){
for(int n=0;n<3;n++){
if(board[xi_Sudoku + m][yi_Sudoku + n] != '.'){
int t = (int)(board[xi_Sudoku + m][yi_Sudoku + n] - '0');
if(check[t]){
return false;
}
check[t]++;
}
}
}
}
return true;
}

3、解题思路:

需要满足的条件:
1.每一行无重复数字
2.每一列无重复数字
3.每一宫格无重复数字

对于 行/列 无重复数字,我们可以将每一 行/列 每个数字出现的次数记录下来
当同一 行/列 中同一数字出现一次以上则返回false

对于每一个子宫格也可以做类似处理,所以只需要解决怎么将子宫格逐个划分的问题就可以啦~

4、关键语句分析:

1、
1
2
int check[10];
memset(check,0,sizeof(int)*10);

check用于记录数字1-9出现的次数,数组下标代表数字,存放的值代表次数,0号单元不用
memset使用时需要包含头文件string.h,它的作用是给check的sizeof(int)*10个整型空间赋值0

2、
1
2
3
4
5
6
7
8
9
10
11
12
13
//行/列遍历
for(int i=0;i<boardSize;i++){
memset(check,0,sizeof(int)*10);
for(int j=0;j<*boardColSize;j++){
if(board[i][j] != '.'){
int t = (int)(board[i][j] - '0');
if(check[t]){
return false;
}
check[t] = 1;
}
}
}

将每一 行/列 的标记数组初始化以后便开始将这一 行/列 进行遍历,因为没有填的数字用'.'表示,所以我们需要将char类型的数字转成int类型,并判断该数字是否被标记过,若是返回false,若没有被标记,则将该位置标记为1。

3、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//宫格遍历
for(int i=0;i<9;i++){
int xi_Sudoku = (i % 3) * 3;
int yi_Sudoku = (i / 3) * 3;
memset(check,0,sizeof(int)*10);
for(int m=0;m<3;m++){
for(int n=0;n<3;n++){
if(board[xi_Sudoku + m][yi_Sudoku + n] != '.'){
int t = (int)(board[xi_Sudoku + m][yi_Sudoku + n] - '0');
if(check[t]){
return false;
}
check[t]++;
}
}
}
}

int xi_Sudoku = (i % 3) * 3;
int yi_Sudoku = (i / 3) * 3;
这两句用划分分子宫格的起始位置
下边两个0-3的for循环用于限定子宫格的大小
在这个题目中相当于将数独划分成了9个小宫格,我们可以通过先找到每个小宫格的起始位置,再利用for确定切题的大小解决划分问题。
其余的标记算法和行/列的大同小异。

二、矩阵置零

1、题目:

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。

2、代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void setZeroes(int** matrix, int matrixSize, int* matrixColSize){
int check_row[matrixSize];
int check_col[*matrixColSize];
memset(check_row,0,sizeof(int)*matrixSize);
memset(check_col,0,sizeof(int)*(*matrixColSize));
//遍历并标记
for(int i=0;i<matrixSize;i++){
for(int j=0;j<(*matrixColSize);j++){
if(matrix[i][j] == 0){
check_row[i] = 1;
check_col[j] = 1;
}
}
}
//条件清零
for(int i=0;i<matrixSize;i++){
for(int j=0;j<(*matrixColSize);j++){
if(check_col[j] || check_row[i])
matrix[i][j] = 0;
}
}
}

3、解题思路:

先遍历一遍数组将存在0的所在行和列全部标记下来
再遍历一遍数组将标记位置清零

4、关键语句分析:

1、
1
2
3
4
int check_row[matrixSize];
int check_col[*matrixColSize];
memset(check_row,0,sizeof(int)*matrixSize);
memset(check_col,0,sizeof(int)*(*matrixColSize));

创建标记数组并初始化清零,和上一题类似

2、
1
2
3
4
5
6
7
8
9
//遍历并标记
for(int i=0;i<matrixSize;i++){
for(int j=0;j<(*matrixColSize);j++){
if(matrix[i][j] == 0){
check_row[i] = 1;
check_col[j] = 1;
}
}
}
3、
1
2
3
4
5
6
7
//条件清零
for(int i=0;i<matrixSize;i++){
for(int j=0;j<(*matrixColSize);j++){
if(check_col[j] || check_row[i])
matrix[i][j] = 0;
}
}

这里其实是通过遍历标记数组对目标矩阵进行清零
既 如果该元素所在的行或列被标记时,就执行清零操作。