▶ 有关数独的两个问题。

▶ 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 。

 class Solution
{
public:
bool isValidSudoku(vector<vector<char>>& board)
{
int index, i, row, col;
char temp;
for (index = ; index < ; index++)
{
row = index / ;
col = index % ;
if ((temp = board[index / ][index % ]) < '' || temp > '')
continue;
temp = board[row][col];
board[row][col] = '';
for (i = ; i < ; i++)
{
if (board[row][i] == temp || board[i][col] == temp || board[row / * + i / ][col / * + i % ] == temp)
return false;
}
board[row][col] = temp;
}
return true;
}
};

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

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

 class Solution
{
public:
bool flag = false;
void cal(vector<vector<char>>& board, int index)
{
int i, j, row, col;
char temp;
bool permit;
for (i = index; i < && (temp = board[i / ][i % ] >= '') && temp <= ''; i++);
if (i == )
{
flag = true;
return;
}
for (temp = ''; temp <= '' && !flag; temp++)
{
for (j = , row = i / , col = i % , permit = true; j < ; j++)
{
if (board[row][j] == temp || board[j][col] == temp || board[row / * + j / ][col / * + j % ] == temp)
{
permit = false;
break;
}
}
if (permit)
{
board[i / ][i % ] = temp;
cal(board, i + );
}
}
if (temp > '' && !flag)
board[i / ][i % ] = '+';
return;
}
void solveSudoku(vector<vector<char>>& board)
{
cal(board, );
return;
}
};

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

 class Solution
{
public:
bool cal(vector<vector<char>>& board, int index)
{
int i, j, row, col;
char temp;
bool conflict, finish;
for (i = index; i < && (temp = board[i / ][i % ] >= '') && temp <= ''; i++); // 寻找下一个没有数字的格点
if (i == )
return true;
for (temp = '', finish = false; temp <= '' && !finish; temp++) // 尝试填写 '1' 到 '9'
{
for (j = , row = i / , col = i % , conflict = false; j < ; j++) // 对欲填入的数字进行三种检查
{
if (board[row][j] == temp || board[j][col] == temp || board[row / * + j / ][col / * + j % ] == temp)
{
conflict = true;
break;
}
}
if (!conflict) // 通过了三种检查,确定填入数字
{
board[i / ][i % ] = temp;
finish = cal(board, i + ); // 在填入该数字的基础上尝试填写下一个
}
}
if (temp > '' && !finish) // 有错,回溯到前面格点重新填写
{
board[i / ][i % ] = '';
return false;
}
return true;
}
bool isValidSudoku(vector<vector<char>>& board)
{
int index, i, row, col;
char temp;
for (index = ; index < ; index++)
{
row = index / ;
col = index % ;
if ((temp = board[index / ][index % ]) < '' || temp > '')
continue;
temp = board[row][col];
board[row][col] = '';
for (i = ; i < ; i++)
{
if (board[row][i] == temp || board[i][col] == temp || board[row / * + i / ][col / * + i % ] == temp)
return false;
}
board[row][col] = temp;
}
return true;
}
void solveSudoku(vector<vector<char>>& board)
{
isValidSudoku(board);
cal(board, );
return;
}
};

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

 using _2D_bit10 = array<array<bitset<>, >, >;
const int ORIGIN_STATE = ; // states 的初始值,后面有解释 class Solution
{
public: // 函数调用关系: solveSudoku{ set, dfs }, dfs{ set, dfs }, set{ constraint }, constraint{ };
int defined_cnt = ; // 已填写的格点数目
bool constraint(_2D_bit10 & states, array<array<char, >, > & bd, const int r, const int c, const int v)
{ // 检查 bd[r][c] 的值是否不等于 v,即当 bd[r][c] == v 时返回 false,认为有矛盾
bitset<> & st = states[r][c];
if (bd[r][c] != ) // 该位置上已经有数字
{
if (bd[r][c] == v) // 与已经有的数字重复,矛盾
return false;
else // 与已经有的数字不重复,通过
return true;
}
st[v] = ; // 该位置上没有数字,说明是在填充其他格子的时候进行的检查,那么 bd[r][c] 就不再可能为 v 了
if (st.count() == ) // bd[r][c] 一个能填的都不剩了,矛盾
return false;
if (st.count() > ) // bd[r][c] 还剩填充其他数字的可能性,通过
return true;
for (int i = ; i <= ; ++i) // 当且仅当 st 中只有一个 1 位时进入,
{
if (st[i] == )
return set(states, bd, r, c, i);// 检查最后剩余的这一种可能是否有矛盾
}
}
bool set(_2D_bit10 & states, array<array<char, >, > & bd, const int r, const int c, const int v)
{ // 在 bd[r][c] 尝试填入 v,检查是否有矛盾
bitset<> & possib = states[r][c];
int k, rr, cc;
possib = ;
bd[r][c] = v;
defined_cnt++;
const int blk_r = (r / ) * , blk_c = (c / ) * ; // bd[r][c] 所在的块段号
for (k = ; k < ; ++k)
{
if (c != k && !constraint(states, bd, r, k, v)) // 同行逐列检查
return false;
if (r != k && !constraint(states, bd, k, c, v)) // 同列逐行检查
return false;
rr = blk_r + k / , cc = blk_c + k % ; // 同块逐格检查
if ((rr != r || cc != c) && !constraint(states, bd, rr, cc, v))
return false;
}
return true;
}
bool dfs(const int i, vector<pair<int, int>> & unset_pts, array<array<char, >, > & bd, _2D_bit10 & states)
{
if (i == unset_pts.size() || * == defined_cnt) // i 为遍历深度,当达到最深层或者已填充的格子数等于 81 时结束遍历(此时所有层遍历均返回)
return true;
const int r = unset_pts[i].first, c = unset_pts[i].second, defined_cnt_copy = defined_cnt;// 取出行列号和备份数据
auto snap_shot_bd = bd;
auto snap_shot_st = states;
bitset<> & st = states[r][c];
if (bd[r][c] != ) // ?当前位置已经有数字了,尝试
return dfs(i + , unset_pts, bd, states);
for (int v = ; v <= ; ++v)// 尝试向bd[r][c] 中填入 v,候选的 v 经由 states[r][c] 即这里的 st 筛选
{
if (st[v])
{
if (set(states, bd, r, c, v) && dfs(i + , unset_pts, bd, states))// 尝试填写 v 成功
return true;
bd = snap_shot_bd; // 还原 bd,states,defined_cnt,清洗掉更深入的遍历导致的写入
states = snap_shot_st;
defined_cnt = defined_cnt_copy;
}
}
return false;
}
void solveSudoku(vector<vector<char>>& board)
{
_2D_bit10 states; // 每个格点可能填充数字表
array<array<char, >, > bd; // 当前盘面
vector<pair<int, int>> unset_pts; // 所有空着的格子的行列号
for (int r = ; r < ; ++r) // 初始化 states,bd
{
for (int c = ; c < ; ++c)
{
states[r][c] = ORIGIN_STATE; // states 每个元素赋值为 1111111110 各位分别对应 9 ~ 0
bd[r][c] = (board[r][c] == '.' ? : board[r][c] - ''); // board(.123456789) → bd(0123456789)
}
}
for (int r = ; r < ; ++r) // 检查原盘面,若存在矛盾则抛出异常(assert(false);)
{
for (int c = ; c < ; ++c)
{
if (bd[r][c] != )
assert(set(states, bd, r, c, bd[r][c]));
}
}
if (defined_cnt == * ) // 已填充的格子数等于 81,数独已经完成
return;
for (int r = ; r < ; ++r) // 初始化 unset_pts
{
for (int c = ; c < ; ++c)
{
if (bd[r][c] == )
unset_pts.emplace_back(r, c);
}
} auto cmp_pt = [states](const pair<int, int> &l, const pair<int, int> & r)// 用于排序的比较函数,按照每个格子可能填充数字的个数升序排列
{
return states[l.first][l.second].count() < states[r.first][r.second].count();
};
std::sort(unset_pts.begin(), unset_pts.end(), cmp_pt);// 对所有空着的格子进行排序,情况数较少的靠前
assert(dfs(, unset_pts, bd, states)); // 尝试对空着的格子进行深度优先遍历,遍历失败(出现矛盾)则抛出异常
for (int r = ; r < ; ++r) // 取出 bd 的数据填写 board
{
for (int c = ; c < ; ++c)
board[r][c] = bd[r][c] + '';
}
return;
}
};

36. Valid Sudoku + 37. Sudoku Solver的更多相关文章

  1. leetcode 37. Sudoku Solver 36. Valid Sudoku 数独问题

    三星机试也考了类似的题目,只不过是要针对给出的数独修改其中三个错误数字,总过10个测试用例只过了3个与世界500强无缘了 36. Valid Sudoku Determine if a Sudoku ...

  2. LeetCode:36. Valid Sudoku,数独是否有效

    LeetCode:36. Valid Sudoku,数独是否有效 : 题目: LeetCode:36. Valid Sudoku 描述: Determine if a Sudoku is valid, ...

  3. [LeetCode] 36. Valid Sudoku 验证数独

    Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according to th ...

  4. [Leetcode][Python]37: Sudoku Solver

    # -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 37: Sudoku Solverhttps://oj.leetcode.co ...

  5. [Leetcode][Python]36: Valid Sudoku

    # -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 36: Valid Sudokuhttps://oj.leetcode.com ...

  6. 【LeetCode题意分析&解答】37. Sudoku Solver

    Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by th ...

  7. 【LeetCode】36. Valid Sudoku 解题报告(Python)

    [LeetCode]36. Valid Sudoku 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址 ...

  8. LeetCode 36 Valid Sudoku

    Problem: Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku board ...

  9. 36. Valid Sudoku

    ============= Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku b ...

随机推荐

  1. JSP 隐含对象

    JSP 隐含对象 JSP隐含对象是JSP容器为每个页面提供的Java对象,开发者可以直接使用它们而不用显式声明.JSP隐含对象也被称为预定义变量. JSP所支持的九大隐含对象: 对象描述 reques ...

  2. WPF资源

    在WPF中.有着两种资源, 一种是组件资源:又被称为程序集资源,以二进制存在编译后的程序集中,通常用于存放图片或其他音频文件. 第二种是对象资源:通常放于xaml中.比如WPF的样式和数据绑定特性. ...

  3. 80端口未被占用,无法启动wamp的解决方法(原创)

    起床之后想要弄弄侧边栏的东西,打开wamp居然无法启动apache服务,上网查了之后才知道是需要启动httpd.exe这个程序,测试了很久,80端口也没有被占用,点击启动apache服务的时候弹出co ...

  4. iTerm2的设置和Zsh.

    很好的说明文: https://xiaozhou.net/learn-the-command-line-iterm-and-zsh-2017-06-23.html iTerm2是Mac os用户使用的 ...

  5. mongodb 之linux下安装、启动、停止、连接

    今天在linux上面安装了mongodb 1.下载linux的mongodb 2.在目录usr/local下创建文件夹mongodb,把安装包解压到该文件夹中 # mkdir mongodb # ta ...

  6. UVALive-5095 Transportation (最小费用流+拆边)

    题目大意:有n个点,m条单向边.要运k单位货物从1到n,但是每条道路上都有一个参数ai,表示经这条路运送x个单位货物需要花费ai*x*x个单位的钱.求最小费用. 题目分析:拆边.例如:u到v的容量为5 ...

  7. 【Python】operator 模块简单介绍

    简单介绍几个常用的函数,其他的请参考文档. operator.concat(a, b) **operator.__concat__(a, b)** 对于 a.b序列,返回 a + b(列表合并) -- ...

  8. ActiveX开发

    转自(http://blog.csdn.net/mingojiang/article/details/8159263) 一.ActiveX基础 1.1什么是ActiveX ActiveX是COM规范的 ...

  9. android adapter的性能小结

    一般adapter的做法会重写getView方法 比如 @Override public View getView(int position, View convertView, ViewGroup ...

  10. 内存泄漏 之 MAT工具的使用

    1 内存泄漏的排查方法 Dalvik Debug Monitor Server (DDMS) 是 ADT插件的一部分,其中有两项功能可用于内存检查 : ·    heap 查看堆的分配情况 ·     ...