文章目录
一、题目
1. 题目描述
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
- 数字 1-9 在每一行只能出现一次。
- 数字 1-9 在每一列只能出现一次。
- 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
2. 示例
示例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 形式的。
3. 题目解析
接触的第一道中等难度题,第一思路是借助HashMap的两层遍历,并且要进行三次两层遍历:
第一次:判断行冲突。
第二次:判断列冲突。
第三次:判断块冲突。
二、解法
1. 个人解法
(1)代码
/**
* 第一次:块儿判断出错
*/
public class Arrayshudu {
boolean isBalidSudoku1(char[][] board) {
Map<Character, Integer> map = new HashMap<>();
// 进行行判断
for (int i = 0; i < 9; i++) {
// 每行结束都要重置HashMap
map.clear();
for (int j = 0; j < 9; j++) {
// 有重复元素,那就返回false
if (map.containsKey(board[i][j])) {
return false;
// 点元素排除,其余非重复元素放HashMap
} else if(board[i][j]!='.'){
map.put(board[i][j], 1);
}
}
}
// 进行列判断
for (int k = 0; k < 9; k++) {
// 每列结束都要重置HashMap
map.clear();
for (int l = 0; l < 9; l++) {
// 有重复元素,那就返回false
if (map.containsKey(board[l][k])) {
return false;
// 点元素排除,其余非重复元素放HashMap
} else if(board[l][k]!='.') {
map.put(board[l][k], 1);
}
}
}
int flag = 0;
int heng = 0;
int shu = 0;
// flag表示块
while (flag < 9) {
// 每块判断结束后要重置HashMap
map.clear();
// 遍历块内元素
for (int i = heng; i < heng + 3; i++) {
for (int j = shu; j < shu + 3; j++) {
// 有重复元素,那就返回false
if (map.containsKey(board[i][j])) {
return false;
// 点元素排除,其余非重复元素放HashMap
} else if(board[i][j]!='.') {
map.put(board[i][j], 1);
}
}
}
// 找下一个块的起点
heng = (heng + 3) % 9;
shu = (shu + 3) % 9;
flag++;
}
return true;
}
(2)失败反思
个人解法说明: 进行三次数独的整体遍历
第一次:遍历判断有没有行冲突。
第二次:遍历判断有没有列冲突。
第三次:遍历判断有没有块冲突。
失败原因:
进行块冲突判断的过程中,一个块遍历结束后,寻找下一个块的起点,这个步骤出错。
反思:
在做题过程中,应该多加一些中间过程输出,来避免出错,最后提交的时候去掉即可。
(3)改进
旧版找块起点代码:
// 找下一个块的起点
heng = (heng + 3) % 9;
shu = (shu + 3) % 9;
执行过程中,其实一直在判断第一块的重复情况,起点并未发生变化。
修改后:
System.out.println(heng+"||||"+shu);
heng = (flag + 1) / 3*3;
shu = (shu + 3) % 9;
运行结果:
从运行结果可看出,修改后的代码,在运行过程中,每个块的起点元素可以正确被寻到。
(4)算法分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
(5)提交截图
第一次失败截图:
第二次成功截图:
2. 官网高星解法
(1)三合一
解法分析
分三次遍历,只是为了写代码的时候思路清晰,不容易出错,高星解法就是把三次遍历给整合为一了。由于在一次遍历里解决,不好进行HashMap的重复利用,因此需要申请很多的HashMap。
可以借鉴的操作:
利用Vlaue,在进行HashMap插入的同时判断是否存在相同的Key:
rows[i].put(n, rows[i].getOrDefault(n, 0) + 1);
这样通过value值就可以知道该元素是否重复。
代码
/**
* 高星解法,二维哈希数组,一次双层遍历搞定
*
* @param board
* @return
*/
boolean isBalidSudoku3(char[][] board) {
// init data
HashMap<Integer, Integer>[] rows = new HashMap[9];
HashMap<Integer, Integer>[] columns = new HashMap[9];
HashMap<Integer, Integer>[] boxes = new HashMap[9];
//因为放到一次遍历里实现,不好清空hashmap,所以直接多申请点。
for (int i = 0; i < 9; i++) {
rows[i] = new HashMap<Integer, Integer>();
columns[i] = new HashMap<Integer, Integer>();
boxes[i] = new HashMap<Integer, Integer>();
}
// validate a board
// 每遍历到一个新元素,判断下三个hashmap里是否发生重复
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char num = board[i][j];
if (num != '.') {
int n = (int) num;
// 为了控制子数独
int box_index = (i / 3) * 3 + j / 3;
// keep the current cell value
// getOrDefault(a,b)若map里存在key=a,则返回对应value,若不存在a,则返回b
// put的同时判断了是否有重复元素
rows[i].put(n, rows[i].getOrDefault(n, 0) + 1);
columns[j].put(n, columns[j].getOrDefault(n, 0) + 1);
boxes[box_index].put(n, boxes[box_index].getOrDefault(n, 0) + 1);
// check if this value has been already seen before
if (rows[i].get(n) > 1 || columns[j].get(n) > 1 || boxes[box_index].get(n) > 1)
return false;
}
}
}
return true;
}
算法分析
高星解法后面给出的时间复杂度和空间复杂度都是
O
(
1
)
O(1)
O(1),可能是考虑到n=9,所以都是常数级操作。
但是毕竟进行了一次两层遍历,我认为真正的时间复杂度应该为:
O
(
n
2
)
O(n^2)
O(n2),申请了3N个长度为N的HashMap,因此空间复杂度应该为
O
(
n
2
)
O(n^2)
O(n2)