面向小白的力扣37. 解数独

270 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

今天,我们继续搞算法。

题目描述

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空格内已填入了数字,空白格用 '.' 表示。

image.png

题目分析

这个题目是要我们解数独,我第一次看到这么多密密麻麻的数字我也是一头雾水,感觉应该是道困难题,哈哈,正如大家所见,但是我们不能北困难吓倒,因为我们已经有了一个验证数独的代码,所以,我们怎么样,把这个代码作为数独的验证,然后我们枚举一个数填进去不就好了嘛,本着让大家理解的方式,本题采用模块话的方式帮助大家解决这个题。

那我们的思路就很清晰了,第一步找到一个空,然后枚举判断是否有效,然后还原现场。为啥要还原现场呢?因为你枚举不一定是对的呀,如果是ture那就返回ture,如果是fasle呢?还得把原来的改回来,再接着枚举,说干就干。

之后呢,就干超时了

image.png

为啥超时呢?因为你可能性太多了,第一个枚举1-9,最后是9的81次方,所以我们干什么呢?剪枝。

我们这样来考虑,不是搜索空位置,而是搜索最小可能性,枚举i,j,如果是空,我们就看看这个位置能填什么数,我们选一个最小的位置填入,先把这个位置返回填入,然后不是枚举1-9,是枚举这个位置可能的数字,如果填了,我们就做个行列块标记,如果填之后不满足,那我们就需要回溯,重新填写,直到填完所有位置。

最终结果:

image.png

解题思路

  • 确定操作对象:本题中,操作对象1个root,1个board
  • 确定操作条件:这个题操作条件很多,关键是看这个位置有多少数能填,返回一个最小的位置进行填写,之后剩下的分支就会少。
  • 确定操作过程:操作过程就是枚举可能填入的数字,依次填入。
  • 确定结果返回:返回最终结果。

代码

class Solution {
    private Boolean[][] rowFlag = new Boolean[9][10]; 
    private Boolean[][] colFlag= new Boolean[9][10]; 
    private Boolean[][]  boxFlag = new Boolean[9][10]; 
    public void solveSudoku(char[][] board) {
          int m = board.length;
         for(int i =0;i<9;i++){
             for(int digit=1;digit<=9;digit++){
                 rowFlag[i][digit]=true;
                 colFlag[i][digit]=true;
                 boxFlag[i][digit] =true;
             }
    }

    for(int i=0;i<9;i++){
        for(int j=0;j<9;j++){
            int ch = board[i][j];
            if(ch == '.') continue;
            int index = ch - '0';
            rowFlag[i][index]=false;
            colFlag[j][index]=false;
            boxFlag[boxIndex(i,j)][index] =false;
           
        }
    }
 dfs(board);

}

public int boxIndex(int i ,int j){
    return (i/3)*3+(j/3);
}

     public Boolean dfs(char[][] board){
        //  if(!isValidSudoku(board)) return false;
        int[] arr = searchMiniPossablity(board);
        int x = arr[0];
        int y = arr[1];
        if(x == -1) return true;
        List<Integer> digits = getAvailableDigits(x,y);
        for(Integer digit: digits){
            board[x][y]=(char)('0'+digit.intValue());
              rowFlag[x][digit]=false;
              colFlag[y][digit]=false;
              boxFlag[boxIndex(x,y)][digit] =false;
            if(dfs(board)) return true;
            board[x][y]='.';
            rowFlag[x][digit]=true;
            colFlag[y][digit]=true;
            boxFlag[boxIndex(x,y)][digit] =true;

     }
     return false;
}

     public List<Integer> getAvailableDigits(int i, int j){
          List<Integer> getAvailableDigits = new ArrayList<>();
             for(int digit=1;digit<=9;digit++){
                if(rowFlag[i][digit] && colFlag[j][digit] && boxFlag[boxIndex(i,j)][digit]){
                    getAvailableDigits.add(digit);
                }
             }
             return getAvailableDigits;
         
     }

     public int[] searchMiniPossablity(char[][] board){
         int miniValue = 10;
         int[] arr = new int[2];
         arr[0] = -1;
         arr[1] = -1;
         int m = board.length;
         int n = board[0].length;
         for(int i =0;i<m;i++){
             for(int j=0;j<n;j++){
             if(board[i][j] != '.') continue;
              List list =  getAvailableDigits(i,j);
              if(list.size()<= miniValue){
                  miniValue = list.size();
                  arr[0] = i;
                  arr[1]= j;
                  
              }
             }
         }

         
         return arr;
     }
}