zoukankan      html  css  js  c++  java
  • 36. Valid Sudoku + 37. Sudoku Solver

    ▶ 有关数独的两个问题。

    ▶ 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 };
  • 相关阅读:
    HttpContext.GetOwinContext().Authentication 报错 解决办法
    owin Claims-based认证登录实现
    angularjs初识ng-app、ng-model、ng-repeat指令
    SpringBoot配置slf4j logback-spring.xml日志
    idea时间注释模版
    oracel截取字符串
    win10官网下载地址
    使用HttpWebRequest实现basic身份认证
    mybatis常用jdbcType数据类型与mysql的类型对照
    修改IntelliJ IDEA 默认配置路径
  • 原文地址:https://www.cnblogs.com/cuancuancuanhao/p/8283426.html
Copyright © 2011-2022 走看看