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 。
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的更多相关文章
- leetcode 37. Sudoku Solver 36. Valid Sudoku 数独问题
三星机试也考了类似的题目,只不过是要针对给出的数独修改其中三个错误数字,总过10个测试用例只过了3个与世界500强无缘了 36. Valid Sudoku Determine if a Sudoku ...
- LeetCode:36. Valid Sudoku,数独是否有效
LeetCode:36. Valid Sudoku,数独是否有效 : 题目: LeetCode:36. Valid Sudoku 描述: Determine if a Sudoku is valid, ...
- [LeetCode] 36. Valid Sudoku 验证数独
Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according to th ...
- [Leetcode][Python]37: Sudoku Solver
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 37: Sudoku Solverhttps://oj.leetcode.co ...
- [Leetcode][Python]36: Valid Sudoku
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 36: Valid Sudokuhttps://oj.leetcode.com ...
- 【LeetCode题意分析&解答】37. Sudoku Solver
Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by th ...
- 【LeetCode】36. Valid Sudoku 解题报告(Python)
[LeetCode]36. Valid Sudoku 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址 ...
- LeetCode 36 Valid Sudoku
Problem: Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku board ...
- 36. Valid Sudoku
============= Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku b ...
随机推荐
- 理解 Git 的基本概念 ( Merging Collaborating Rebasing)
合并 Merging 在分支上开发新功能后,如何把新功能加入到主分支,让其它人得到你的修改呢?你需要使用命令 git merge 或 git pull. 这两个命令的语法如下: git merge [ ...
- windows 上 Python 通过 SCP 连接linux server
环境搭建 需要安装以下包 pycrypto (需要VC编译环境) paramiko (SSH的基础) scpclient-0.4 (paramiko 上的一个wrapper) 在安装pycrypto ...
- ibatis.net 实现多数据库配置
1.1 功能介绍 使用ibatis.net ORM框架时,有时候需要操作多个数据库,同时有时候也需要对连接数据库信息进行加密,本文通过将配置连接写到Web.config中, 这样就可以在Web.co ...
- HIVE之正则化详解
有大神写的很好了,我借花献佛,有兴趣,看链接,在此不再赘述.想要学习Hive正则表达式重点应该是正则表达式的表示方式,只有正则表达式使用溜了,hive正则那就是小case. 附参考博文: https: ...
- C#金额千分位分隔
"234234".ToString("C") 显示:¥234,234
- Linux 需要掌握的一些命令
详情: 1. tar 创建一个新的tar文件 $ tar cvf archive_name.tar dirname/ 解压tar文件 $ tar xvf archive_name.tar 查看tar文 ...
- I.MX6 Linux 3.0.35 SD boot
/********************************************************************************* * I.MX6 Linux 3.0 ...
- 如何检测MySQL中建立的索引是否生效
使用方法,在select语句前加上explain就可以了: EXPLAIN SELECT surname,first_name form a,b WHERE a.id=b.id EXPLAIN列的解释 ...
- 494 - Kindergarten Counting Game
Kindergarten Counting Game Everybody sit down in a circle. Ok. Listen to me carefully. ``Woooooo, ...
- QLoo graphql engine了解
参考架构图 处理流程 使用gloo注册服务api 发现断电以及serverless 函数 更新graphql schema 在qloo的resolvermap 中连接schema定义的字段 特性 不用 ...