【leetcode】sudokuSolver數獨解題

0.摘要c++

小時候在報紙上玩過數獨,那時候以爲很難,前幾天在leetcode上遇到了這個題,挺有意思因而記錄下來數組

通常一道數獨題,就像他給的例子這樣,9*9的格子,知足 行,列 ,宮均取1-9的數,切互不相同。函數

   

那通常正常人的思路會去一點一點的推理,至少我小時候就是這麼玩的,具體來講,比如 r7c9(第7行,第9列)的空格,我會找第7行有『6,2,8』,第9列有『3,1,6,5,9』,第9宮有『2,8,5,7,9』,這些的並集就是『1,2,3,5,6,7,8,9』,哦那麼空格是4。就這麼一點點繼續往下推理。spa

1.餘數法
code

他給的函數接口是這樣blog

void solveSudoku(vector<vector<char>>& board){}遞歸

而後我就照着我小時候的思路寫了一個版本接口

 1 void solveSudoku(vector<vector<char>>& board) {
 2     if(board.size()!=9||board[0].size()!=9)
 3         return;
 4     vector<vector<vector<bool>>> ex(9,vector<vector<bool>>(9,vector<bool>(9,false)));
 5     vector<vector<int>> count(9,vector<int>(9,0));
 6     queue<int> se;
 7     for(int i=0;i<9;i++)
 8     {
 9         for(int j=0;j<9;j++)
10             if(board[i][j]=='.')
11             {                    
12                 int block_i = i/3;
13                 int block_j = j/3;
14                 for(int c=0;c<9;c++)
15                 {
16                      if(board[i][c]!='.'&&!ex[i][j][board[i][c]-'1'])
17                     {
18                         ex[i][j][board[i][c]-'1'] = true;
19                         count[i][j]++;
20                     }
21                     if(board[c][j]!='.'&&!ex[i][j][board[c][j]-'1'])
22                     {
23                         ex[i][j][board[c][j]-'1'] = true;
24                         count[i][j]++;
25                     }
26                     int ii = block_i*3 + c/3;
27                     int jj = block_j*3 + c%3;
28                     if(board[ii][jj]!='.'&&!ex[i][j][board[ii][jj]-'1'])
29                     {
30                         ex[i][j][board[ii][jj]-'1'] = true;
31                         count[i][j]++;
32                     }
33                 }
34                 if(count[i][j]==8)
35                     se.push(i*9+j);
36             }
37     }
38     while(!se.empty())
39     {
40         int cur = se.front();
41         se.pop();
42         int i = cur/9;
43         int j = cur%9;
44         int block_i = i/3;
45         int block_j = j/3;
46         for(int c=0;c<9;c++)
47             if(!ex[i][j][c])
48             {
49                 board[i][j] = c + '1';
50                 break;
51             }
52         for(int c=0;c<9;c++)
53         {
54             if(board[i][c]=='.'&&!ex[i][c][board[i][j]-'1'])
55             {
56                 ex[i][c][board[i][j]-'1'] = true;
57                 count[i][c]++;
58                 if(count[i][c]==8)
59                     se.push(i*9+c);
60             }
61             if(board[c][j]=='.'&&!ex[c][j][board[i][j]-'1'])
62             {
63                 ex[c][j][board[i][j]-'1'] = true;
64                 count[c][j]++;
65                 if(count[c][j]==8)
66                     se.push(c*9+j);
67             }
68             int ii = block_i*3 + c/3;
69             int jj = block_j*3 + c%3;
70             if(board[ii][jj]=='.'&&!ex[ii][jj][board[i][j]-'1'])
71             {
72                 ex[ii][jj][board[i][j]-'1'] = true;
73                 count[ii][jj]++;
74                 if(count[ii][jj]==8)
75                     se.push(ii*9+jj);
76             }
77         }
78     }
79 }

這裏ex是9*9*9的數組,對於非空格的位置ex沒有意義,對於ricj的空格(例如r7c9的空格),ex[i][j][9]是一個bool[9]的數組,分別表明跟ricj相關的20個格子(行列宮一共20格)是否包含x {x=1...9};若是包含x,那麼ex[i][j][x-1]就是true(例如ex[7][9][0,1,2,4,5,6,7,8]爲true,ex[7][9][3]爲false),同時爲了方便創建一個count[9][9]記錄true的個數,count[i][j]記錄ex[i][j]中true的個數,一旦count[i][j]==8,那麼這個格子就能夠推理出來。隊列

那麼剛開始先對整個數組掃描一遍,分別記錄一遍ex和count,找到那些count==8的,放入一個隊列。而後得到隊列的對首ricj,把他的值填入(例如r7c9填」4「),同時找到與r7c9相關20個格子中是空格的位置,更新他們的ex和count(例如r1c9的ex[0][8][4-1]改成true),把count==8的push到對尾,如此往復,直到隊列爲空。leetcode

我當時的想法時隊列空了應該就能解出來了吧。。。因而submit了,結果過了兩個case,還有的case報錯了。。。

 

咦?沒解完。。解了的都對了。。我把剩下的手抄了一下,發現確實解不了,不是程序問題,原來我小時候一直解不出來是有緣由的,不是我眼神很差,方法有問題。遂百度了一下,原來個人方法叫作」餘數法「。餘數法求解不了全部的數獨問題,難的須要假設來推到出矛盾。但怎麼假設好呢,也百度了一下。

2.遞歸+回溯

網上說的最多的方法,主要仍是遞歸+回溯 暴力求解。

 1 int row[9][9] ;
 2 int col[9][9] ;
 3 int block[9][9] ;
 4 
 5 void solveSudoku(vector<vector<char>>& board) {
 6     if(board.size()!=9||board[0].size()!=9)
 7         return;
 8     memset(row, 0, sizeof(row));
 9     memset(col, 0, sizeof(col));
10     memset(block, 0, sizeof(block));
11     //memset(ex, 0, sizeof(ex));
12     //memset(count, 0, sizeof(count));
13     //推導
14     //forward(board);
15     //假設
16     for(int i=0;i<9;i++)
17         for(int j=0;j<9;j++)
18             if(board[i][j]!='.')
19             {
20                 row[i][board[i][j]-'1']=1;
21                 col[j][board[i][j]-'1']=1;
22                 block[(i/3)*3+j/3][board[i][j]-'1']=1;
23             }
24     assume(board,0,0);
25 }
26 
27 bool assume(vector<vector<char>>& board,int i,int j)
28 {
29     if(i==9)
30         return true;
31     if(board[i][j] != '.')
32         return assume(board,i+(j+1)/9,(j+1)%9);
33     else
34         for(int c=0;c<9;c++)
35         {
36             if(!row[i][c]&&!col[j][c]&&!block[i/3*3+j/3][c])
37             {
38                 board[i][j] = c+'1';
39                 row[i][c] = col[j][c] = block[i/3*3+j/3][c] = 1;
40                 if(assume(board,i+(j+1)/9,(j+1)%9))
41                     return true;
42                 board[i][j] = '.';
43                 row[i][c] = col[j][c] = block[i/3*3+j/3][c] = 0;
44             }
45         }
46         return false;
47 }

這種方法是從另一個角度記錄當前的數獨數組的狀況,維持3個bool類型的數組row[9][9],col[9][9],block[9][9],這裏爲了初始化memset方便設成了int型。row[i][j]的含義是第i行是否含有j(例如初始時r[1-1][5-1]爲真,r[1-1][2-1]爲假,第一行有5沒有2),col,block同理。assume是遞歸函數,每次遇到空格就對他從i = 1開始假設,若是他所在的行,列,宮都沒有i那就設他爲i,繼續遞歸日後填寫,遇到矛盾(某個空格不能取1-9之間任何數)就返回。這樣作就是所謂的暴力求解,這麼作確定是沒問題了,能夠求解出正確結果。提交,ac了,4ms,戰勝了83%的人。。。

 

3.預處理+遞歸+回溯

可是我想,遞歸的複雜度和剩餘格子的總數有指數關係,直接遞歸有點浪費時間,未嘗不先用餘數法給」預處理「一下呢,減小遞歸次數。。因而,兩種方法一塊兒,哦了

  1 int row[9][9] ;
  2 int col[9][9] ;
  3 int block[9][9] ;
  4 void solveSudoku(vector<vector<char>>& board) {
  5     if(board.size()!=9||board[0].size()!=9)
  6         return;
  7     memset(row, 0, sizeof(row));
  8     memset(col, 0, sizeof(col));
  9     memset(block, 0, sizeof(block));
 10     //推導 derivation 對於九宮格中可能性惟一的數 直接求解 減小遞歸次數
 11     //餘數法
 12     forward(board);
 13     for(int i=0;i<9;i++)
 14         for(int j=0;j<9;j++)
 15             if(board[i][j]!='.')
 16             {
 17                 row[i][board[i][j]-'1']=1;
 18                 col[j][board[i][j]-'1']=1;
 19                 block[(i/3)*3+j/3][board[i][j]-'1']=1;
 20             }
 21     //假設 assume 對於可能性不惟一的數 遞歸假設求解
 22     assume(board,0,0);
 23 }
 24 
 25 bool assume(vector<vector<char>>& board,int i,int j)
 26 {
 27     if(i==9)
 28         return true;
 29     if(board[i][j] != '.')
 30         return assume(board,i+(j+1)/9,(j+1)%9);
 31     else
 32         for(int c=0;c<9;c++)
 33         {
 34             if(!row[i][c]&&!col[j][c]&&!block[i/3*3+j/3][c])
 35             {
 36                 board[i][j] = c+'1';
 37                 row[i][c] = col[j][c] = block[i/3*3+j/3][c] = 1;
 38                 if(assume(board,i+(j+1)/9,(j+1)%9))
 39                     return true;
 40                 board[i][j] = '.';
 41                 row[i][c] = col[j][c] = block[i/3*3+j/3][c] = 0;
 42             }
 43         }
 44         return false;
 45 }
 46 void forward(vector<vector<char>>& board)
 47 {
 48     bool ex[9][9][9] ;
 49     int count[9][9] ;
 50     memset(ex, 0, sizeof(ex));
 51     memset(count, 0, sizeof(count));
 52     queue<int> se;
 53     //求解全部可能性惟一的
 54     //get all results with only one possible answer
 55     for(int i=0;i<9;i++)
 56         for(int j=0;j<9;j++)
 57             if(board[i][j]=='.')
 58             {                    
 59                 for(int c=0;c<9;c++)
 60                 {
 61                      if(board[i][c]!='.'&&!ex[i][j][board[i][c]-'1'])
 62                     {
 63                         ex[i][j][board[i][c]-'1'] = true;
 64                         count[i][j]++;
 65                     }
 66                     if(board[c][j]!='.'&&!ex[i][j][board[c][j]-'1'])
 67                     {
 68                         ex[i][j][board[c][j]-'1'] = true;
 69                         count[i][j]++;
 70                     }
 71                     int ii = (i/3)*3 + c/3;
 72                     int jj = (j/3)*3 + c%3;
 73                     if(board[ii][jj]!='.'&&!ex[i][j][board[ii][jj]-'1'])
 74                     {
 75                         ex[i][j][board[ii][jj]-'1'] = true;
 76                         count[i][j]++;
 77                     }
 78                 }
 79                 //答案惟一的 push到隊列
 80                 if(count[i][j]==8)
 81                     se.push(i*9+j);
 82             }
 83     while(!se.empty())
 84     {
 85         int cur = se.front();
 86         se.pop();
 87         int i = cur/9;
 88         int j = cur%9;
 89         for(int c=0;c<9;c++)
 90             if(!ex[i][j][c])
 91             {
 92                 board[i][j] = c + '1';
 93                 break;
 94             }
 95         for(int c=0;c<9;c++)
 96         {
 97             if(board[i][c]=='.'&&!ex[i][c][board[i][j]-'1'])
 98             {
 99                 ex[i][c][board[i][j]-'1'] = true;
100                 count[i][c]++;
101                 if(count[i][c]==8)
102                     se.push(i*9+c);
103             }
104             if(board[c][j]=='.'&&!ex[c][j][board[i][j]-'1'])
105             {
106                 ex[c][j][board[i][j]-'1'] = true;
107                 count[c][j]++;
108                 if(count[c][j]==8)
109                     se.push(c*9+j);
110             }
111             int ii = (i/3)*3 + c/3;
112             int jj = (j/3)*3 + c%3;
113             if(board[ii][jj]=='.'&&!ex[ii][jj][board[i][j]-'1'])
114             {
115                 ex[ii][jj][board[i][j]-'1'] = true;
116                 count[ii][jj]++;
117                 if(count[ii][jj]==8)
118                     se.push(ii*9+jj);
119             }
120         }
121     }
122 }
0ms,ac。

總結就是,餘數法推到減小假設次數 + 遞歸假設求解子問題 。由於問題規模固定是9*9,所以損失的空間複雜度也能接受。

 寫了這個着實激發了我很大的興趣,因而後面又寫了生成題庫的模塊,圖形界面的模塊。。。

相關文章
相關標籤/搜索