▶ 有关数独的两个问题。

▶ 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. JAVA异常处理分析(中)

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

  2. Django框架(上传Excel文件并读取)

    博主今天整理下Django框架中上传Excel文件并读取 博主是要在管理平台中新增用例的维护功能,想着通过上传Excel文件来展示用例,下面是项目的路径图: 首先先建数据库模型 model.py 可以 ...

  3. bzoj2463: [中山市选2009]谁能赢呢? 博弈

    小明和小红经常玩一个博弈游戏.给定一个n×n的棋盘,一个石头被放在棋盘的左上角.他们轮流移动石头.每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问过.谁不能 ...

  4. Hive之基本操作

    1,CREATE table. CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name data_type [COMMENT col ...

  5. UVALive-4452 The Ministers' Major Mess (2-SAT)

    题目大意:有n个问题,m个人来投票,没人最多投4票,问该怎样决定才能使每个人都有超过一半的票数被认可? 题目分析:2-SAT问题.如果某个人投的票数少于2,则这两票军被采纳,如果票数至少三票,则最多有 ...

  6. module.exports和exports

    require 用来加载代码,而 exports 和 module.exports 则用来导出代码.但很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 e ...

  7. C语言中的可变参数函数

    C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...

  8. windows配置Scrapy爬虫框架

    一.环境 Windows10 64位 Python2.7.13 64位 下面的安装步骤最好配置代理,可能会遇到被墙的情况. 二.Python的安装 可以去参考这篇文章:http://blog.csdn ...

  9. Android 遍历全国地区位置(一)

    1.布局 choose_area.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...

  10. Python基础学习----拆包

    拆包,多用在多值参数种. 1.多值参数. 有时候,在函数的参数转递时,不单只传输单个字符的参数,比如有元组和字典的参数,这时候我们就使用多值参数. *args 代表元组的多值参数 *kwargs 代表 ...