▶ 有关数独的两个问题。

▶ 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. K-Means & Sequential Leader Clustering

    2017-12-31 19:08:37 k-平均算法源于信号处理中的一种向量量化方法,现在则更多地作为一种聚类分析方法流行于数据挖掘领域.k-means的目的是:把样本划分到k个聚类中,使得每个点都属 ...

  2. 递归--练习5--noi1751分解因数

    递归--练习5--noi1751分解因数 一.心得 想清楚子问题 想清楚递推表达式 没有全部AC说明还有自己没有想到的位置,试边界情况和查看题目要求 二.题目 1751:分解因数 总时间限制:  10 ...

  3. JAVA异常处理分析(中)

    在使用java异常处理机制时候我们会发现有些异常抛出后可以不需要进行抓取处理,而有些异常必须要进行抓取处理,这是个什么情况呢? 设计理念猜想:      有一些场景的异常,是可以不需要处理或是经常不会 ...

  4. find ... -exec ... {} \; 的解释

    find的特殊功能是能够进行额外的动作,如上图的 find / -type f -name "test.txt" -exec rm {} \;命令 1) {} 代表的是由find找 ...

  5. 排序算法总结(基于Java实现)

    前言 下面会讲到一些简单的排序算法(均基于java实现),并给出实现和效率分析. 使用的基类如下: 注意:抽象函数应为public的,我就不改代码了 public abstract class Sor ...

  6. uva-11021-全概率公式

    https://vjudge.net/problem/UVA-11021 有n个球,每只的存活期都是1天,他死之后有pi的概率产生i个球(0<=i<n),一开始有k个球,问m天之后所有球都 ...

  7. Psping 实例

    PsPing v2.1 https://docs.microsoft.com/zh-cn/sysinternals/downloads/psping 2016/06/29 4 分钟阅读时长 By Ma ...

  8. 关于React setState的实现原理(一)

    前言 首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗? class Root extends React.Component { constru ...

  9. 新书《Cocos2dx 3.x 3D图形学渲染技术讲解》问世

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  10. It is the courage

    It is the reality that a society which becomes lower and becomes weak.Believe it or not,I think it i ...