这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
题目1 - 数独是否成立
链接:leetcode-cn.com/problems/va…
解析
实际上这个问题还是比较简单的:
我们只要按照题目要求:
- 判断行,列,每个3*3的区域内有没有重复数字
即可。
这些数字我们使用布尔值进行存储,并迭代地遍历每一个方格,依次记录-判断,就可以了。
代码
public static boolean isValidSudoku(char[][] board) {
boolean[][] xDics = new boolean[9][10];
boolean[][] yDics = new boolean[9][10];
boolean[][] zDics = new boolean[9][10];
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[x].length; y++) {
if(board[x][y] == '.') continue;
int num = board[x][y]-'0';
if(xDics[x][num]) return false;
else xDics[x][num] = true;
if(yDics[y][num]) return false;
else yDics[y][num] = true;
int zIdx = (3*((x)/3)+(y)/3);
if(zDics[zIdx][num]) return false;
else zDics[zIdx][num] = true;
}
}
return true;
}
题目2 - 解数独
链接:leetcode-cn.com/problems/su… 如果说上面的问题只是让我们了解数独,那么这个问题就是真正想询问我们的问题了:
- 如何解决一个数独问题?
根据上面的数独判断,我们有个基本的想法:
-
先把数独给定的那些结构摸清楚了
-
然后再根据结构,按照顺序一个一个地往里面填。
- 如果错了就回退到上一个可选择的点,换一个数接着往下选,如果选不到恢复状态并接着填下一个数,直到填到一个符合条件的数,或者所有数都没办法填。
核心代码就是这样了:
//从我们上面获取的预设结构出发
boolean[] xDic = xDics[x];
boolean[] yDic = yDics[y];
int zIdx = (3*((x)/3)+(y)/3);
boolean[] zDic = zDics[zIdx];
//因为是数独,因此最多只有9个可能性
for(int i=0;i<xDic.length;i++){
//判断是否符合我们到达这个方块时的限制条件
if(!xDic[i] && !yDic[i] && !zDic[i]){
xDic[i] = yDic[i] = zDic[i] = true;
board[x][y] = (char) (start+i+1);
//符合了:我们就继续下一个格子的填写
if(!fill(board, x+1, y)){
xDic[i] = yDic[i] = zDic[i] = false;
board[x][y] = '.';
}else{
return true;
}
}
}
那么我们优化一下上面探测的相关代码并加上校验,就得到了第一个可以AC的答案:
static boolean[][] xDics = new boolean[9][10];
static boolean[][] yDics = new boolean[9][10];
static boolean[][] zDics = new boolean[9][10];
static final char start = '0';
public static void solveSudoku(char[][] board) {
xDics = new boolean[9][9];
yDics = new boolean[9][9];
zDics = new boolean[9][9];
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[x].length; y++) {
if(board[x][y] == '.') continue;
int num = board[x][y]-'0'-1;
xDics[x][num] = true;
yDics[y][num] = true;
int zIdx = (3*((x)/3)+(y)/3);
zDics[zIdx][num] = true;
}
}
fill(board,0,0);
}
public static boolean fill(char[][] board, int x, int y){
if(x >=9){
x = 0;
y++;
}
if(y >=9){
return true;
}
if(board[x][y]!='.') return fill(board,x+1,y);
boolean[] xDic = xDics[x];
boolean[] yDic = yDics[y];
int zIdx = (3*((x)/3)+(y)/3);
boolean[] zDic = zDics[zIdx];
for(int i=0;i<xDic.length;i++){
if(!xDic[i] && !yDic[i] && !zDic[i]){
xDic[i] = yDic[i] = zDic[i] = true;
board[x][y] = (char) (start+i+1);
if(!fill(board, x+1, y)){
xDic[i] = yDic[i] = zDic[i] = false;
board[x][y] = '.';
}else{
return true;
}
}
}
return false;
}
执行用时:1 ms, 在所有 Java 提交中击败了99.60%的用户
内存消耗:35.8 MB, 在所有 Java 提交中击败了67.17%的用户
思考
我们上面的代码未免有些太多了,那么:
-
我们是否可以合并探测和填空的代码来减少代码量呢?
- 同时要保证我们算法的时间复杂度并没有提升
我们探测的时间复杂度为O(n2),填空的时间复杂度最大为O(n3),因此探测的时间对于填空来说是影响较小的,而且如果缺少了探测作为基础,我们回溯的成本会变得更高。
那么,实际上我们的改进就在数组的记录上了,我们可以:
- 使用三个数字,来记录行列区的当前数字情况,高9位记录当前区域,低9位记录当前区域内的情况。