爨爨爨好

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

▶ 有关数独的两个问题。

▶ 36. 检测当前盘面是否有矛盾(同一行、同一列或 3 × 3 的小框内数字重复),而不关心该盘面是否有解。

● 初版代码,24 ms,没有将格子检测函数独立出去,行检测、列检测、框检测是否合并为一个循环对速度的影响不明显。最快的解法算法与之相同,蜜汁优化 static vector<vector<char>> board = []() {std::ios::sync_with_stdio(false); cin.tie(NULL); return vector<vector<char>>{}; }(); 后变为 7 ms 。

 1 class Solution
 2 {
 3 public:
 4     bool isValidSudoku(vector<vector<char>>& board)
 5     {
 6         int index, i, row, col;
 7         char temp;
 8         for (index = 0; index < 81; index++)
 9         {
10             row = index / 9;
11             col = index % 9;
12             if ((temp = board[index / 9][index % 9]) < '1' || temp > '9')
13                 continue;
14             temp = board[row][col];
15             board[row][col] = '0';                  
16             for (i = 0; i < 9; i++)
17             {
18                 if (board[row][i] == temp || board[i][col] == temp || board[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3] == temp)
19                     return false;
20             }            
21             board[row][col] = temp;
22         }
23         return true;
24     }
25 };

 

▶ 37. 数独求解。总是假设盘面具有唯一解(即求出第一个解即可)。

● 初版代码,13 ms,去掉了第36题中的盘面矛盾检测。

 1 class Solution
 2 { 
 3 public:
 4     bool flag = false;
 5      void cal(vector<vector<char>>& board, int index)
 6     {
 7         int i, j, row, col;
 8         char temp;
 9         bool permit;
10         for (i = index; i < 81 && (temp = board[i / 9][i % 9] >= '1') && temp <= '9'; i++);
11         if (i == 81)
12         {
13             flag = true;
14             return;
15         }
16         for (temp = '1'; temp <= '9' && !flag; temp++)
17         {            
18             for (j = 0, row = i / 9, col = i % 9, permit = true; j < 9; j++)
19             {
20                 if (board[row][j] == temp || board[j][col] == temp || board[row / 3 * 3 + j / 3][col / 3 * 3 + j % 3] == temp)
21                 {
22                     permit = false;
23                     break;
24                 }
25             } 
26             if (permit)
27             {
28                 board[i / 9][i % 9] = temp;
29                 cal(board, i + 1);
30             }
31         }
32         if (temp > '9' && !flag)
33             board[i / 9][i % 9] = '+';
34         return;
35     }
36     void solveSudoku(vector<vector<char>>& board)
37     {
38         cal(board, 0); 
39         return;
40     }
41 };

● 改良代码,10 ms,干掉了变量 flag,在填表前先做第 36 题的矛盾检查。

 1 class Solution
 2 {
 3 public:
 4     bool cal(vector<vector<char>>& board, int index)
 5     {
 6         int i, j, row, col;
 7         char temp;
 8         bool conflict, finish;
 9         for (i = index; i < 81 && (temp = board[i / 9][i % 9] >= '1') && temp <= '9'; i++); // 寻找下一个没有数字的格点
10         if (i == 81)
11             return true;
12         for (temp = '1', finish = false; temp <= '9' && !finish; temp++)        // 尝试填写 '1' 到 '9'
13         {
14             for (j = 0, row = i / 9, col = i % 9, conflict = false; j < 9; j++) // 对欲填入的数字进行三种检查
15             {
16                 if (board[row][j] == temp || board[j][col] == temp || board[row / 3 * 3 + j / 3][col / 3 * 3 + j % 3] == temp)
17                 {
18                     conflict = true;
19                     break;
20                 }
21             }
22             if (!conflict)                                                      // 通过了三种检查,确定填入数字
23             {
24                 board[i / 9][i % 9] = temp;
25                 finish = cal(board, i + 1);                                     // 在填入该数字的基础上尝试填写下一个
26             }
27         }
28         if (temp > '9' && !finish)                                              // 有错,回溯到前面格点重新填写
29         {
30             board[i / 9][i % 9] = '0';
31             return false;
32         }
33         return true;
34     }
35     bool isValidSudoku(vector<vector<char>>& board)
36     {
37         int index, i, row, col;
38         char temp;
39         for (index = 0; index < 81; index++)
40         {
41             row = index / 9;
42             col = index % 9;
43             if ((temp = board[index / 9][index % 9]) < '1' || temp > '9')
44                 continue;
45             temp = board[row][col];
46             board[row][col] = '0';
47             for (i = 0; i < 9; i++)
48             {
49                 if (board[row][i] == temp || board[i][col] == temp || board[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3] == temp)
50                     return false;
51             }
52             board[row][col] = temp;
53         }
54         return true;
55     }
56     void solveSudoku(vector<vector<char>>& board)
57     {
58         isValidSudoku(board);
59         cal(board, 0);
60         return;
61     }
62 };

● 大佬代码, 0 ms,主要是使用一个 array<array<bitset<10>, 9>, 9> 结构的变量来保存所有格点可能填写的数字,使用一个 array<array<char, 9>, 9> 结构的变量来保存当前盘面,使用一个 vector<pair<int, int>> 结构的变量来保存所有空个点的位置,通过调整深度优先的便利顺序,减少了时间开销。

  1 using _2D_bit10 = array<array<bitset<10>, 9>, 9>;
  2 const int ORIGIN_STATE = 1022;          // states 的初始值,后面有解释
  3 
  4 class Solution
  5 {
  6 public: // 函数调用关系: solveSudoku{ set, dfs }, dfs{ set, dfs }, set{ constraint }, constraint{ };
  7     int defined_cnt = 0;                // 已填写的格点数目    
  8     bool constraint(_2D_bit10 & states, array<array<char, 9>, 9> & bd, const int r, const int c, const int v)
  9     {                                   // 检查 bd[r][c] 的值是否不等于 v,即当 bd[r][c] == v 时返回 false,认为有矛盾
 10         bitset<10> & st = states[r][c];
 11         if (bd[r][c] != 0)              // 该位置上已经有数字
 12         {
 13             if (bd[r][c] == v)          // 与已经有的数字重复,矛盾
 14                 return false;
 15             else                        // 与已经有的数字不重复,通过
 16                 return true;
 17         }
 18         st[v] = 0;                      // 该位置上没有数字,说明是在填充其他格子的时候进行的检查,那么 bd[r][c] 就不再可能为 v 了
 19         if (st.count() == 0)            // bd[r][c] 一个能填的都不剩了,矛盾
 20             return false;
 21         if (st.count() > 1)             // bd[r][c] 还剩填充其他数字的可能性,通过
 22             return true;                   
 23         for (int i = 1; i <= 9; ++i)    // 当且仅当 st 中只有一个 1 位时进入,
 24         {
 25             if (st[i] == 1)
 26                 return set(states, bd, r, c, i);// 检查最后剩余的这一种可能是否有矛盾
 27         }
 28     }
 29     bool set(_2D_bit10 & states, array<array<char, 9>, 9> & bd, const int r, const int c, const int v)
 30     {                                                       // 在 bd[r][c] 尝试填入 v,检查是否有矛盾       
 31         bitset<10> & possib = states[r][c];
 32         int k, rr, cc;
 33         possib = 0;
 34         bd[r][c] = v;
 35         defined_cnt++;
 36         const int blk_r = (r / 3) * 3, blk_c = (c / 3) * 3; // bd[r][c] 所在的块段号
 37         for (k = 0; k < 9; ++k)
 38         {
 39             if (c != k && !constraint(states, bd, r, k, v)) // 同行逐列检查
 40                 return false;
 41             if (r != k && !constraint(states, bd, k, c, v)) // 同列逐行检查
 42                 return false;
 43             rr = blk_r + k / 3, cc = blk_c + k % 3;         // 同块逐格检查
 44             if ((rr != r || cc != c) && !constraint(states, bd, rr, cc, v))
 45                 return false;
 46         }
 47         return true;
 48     }
 49     bool dfs(const int i, vector<pair<int, int>> & unset_pts, array<array<char, 9>, 9> & bd, _2D_bit10 & states)
 50     {
 51         if (i == unset_pts.size() || 9 * 9 == defined_cnt)  // i 为遍历深度,当达到最深层或者已填充的格子数等于 81 时结束遍历(此时所有层遍历均返回)
 52             return true;
 53         const int r = unset_pts[i].first, c = unset_pts[i].second, defined_cnt_copy = defined_cnt;// 取出行列号和备份数据
 54         auto snap_shot_bd = bd;                             
 55         auto snap_shot_st = states;
 56         bitset<10> & st = states[r][c];
 57         if (bd[r][c] != 0)                                  // ?当前位置已经有数字了,尝试
 58             return dfs(i + 1, unset_pts, bd, states);
 59         for (int v = 1; v <= 9; ++v)// 尝试向bd[r][c] 中填入 v,候选的 v 经由 states[r][c] 即这里的 st 筛选
 60         {
 61             if (st[v])
 62             {
 63                 if (set(states, bd, r, c, v) && dfs(i + 1, unset_pts, bd, states))// 尝试填写 v 成功
 64                     return true;
 65                 bd = snap_shot_bd;                          // 还原 bd,states,defined_cnt,清洗掉更深入的遍历导致的写入
 66                 states = snap_shot_st;
 67                 defined_cnt = defined_cnt_copy;
 68             }
 69         }
 70         return false;
 71     }
 72     void solveSudoku(vector<vector<char>>& board)
 73     {
 74         _2D_bit10 states;                   // 每个格点可能填充数字表
 75         array<array<char, 9>, 9> bd;        // 当前盘面
 76         vector<pair<int, int>> unset_pts;   // 所有空着的格子的行列号
 77         for (int r = 0; r < 9; ++r)         // 初始化 states,bd
 78         {
 79             for (int c = 0; c < 9; ++c)
 80             {
 81                 states[r][c] = ORIGIN_STATE;                                // states 每个元素赋值为 1111111110 各位分别对应 9 ~ 0
 82                 bd[r][c] = (board[r][c] == '.' ? 0 : board[r][c] - '0');    // board(.123456789) → bd(0123456789)
 83             }
 84         }
 85         for (int r = 0; r < 9; ++r)         // 检查原盘面,若存在矛盾则抛出异常(assert(false);)
 86         {
 87             for (int c = 0; c < 9; ++c)
 88             {
 89                 if (bd[r][c] != 0)
 90                     assert(set(states, bd, r, c, bd[r][c]));
 91             }
 92         }
 93         if (defined_cnt == 9 * 9)   // 已填充的格子数等于 81,数独已经完成
 94             return;
 95         for (int r = 0; r < 9; ++r) // 初始化 unset_pts
 96         {
 97             for (int c = 0; c < 9; ++c)
 98             {
 99                 if (bd[r][c] == 0)
100                     unset_pts.emplace_back(r, c);
101             }
102         }
103 
104         auto cmp_pt = [states](const pair<int, int> &l, const pair<int, int> & r)// 用于排序的比较函数,按照每个格子可能填充数字的个数升序排列
105         {
106             return states[l.first][l.second].count() < states[r.first][r.second].count();
107         };
108         std::sort(unset_pts.begin(), unset_pts.end(), cmp_pt);// 对所有空着的格子进行排序,情况数较少的靠前
109         assert(dfs(0, unset_pts, bd, states));                // 尝试对空着的格子进行深度优先遍历,遍历失败(出现矛盾)则抛出异常
110         for (int r = 0; r < 9; ++r)                           // 取出 bd 的数据填写 board
111         {
112             for (int c = 0; c < 9; ++c)
113                 board[r][c] = bd[r][c] + '0';
114         }
115         return;
116     }
117 };

 

posted on 2018-01-14 15:32  爨爨爨好  阅读(173)  评论(0编辑  收藏  举报