【leetCode】 - 数独问题

691 阅读2分钟

这是我参与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位记录当前区域内的情况。