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 ...
随机推荐
- CPU上下文切换详解
CPU上下文切换详解 原文地址,译文地址,译者: 董明鑫,校对:郑旭东 上下文切换(有时也称做进程切换或任务切换)是指CPU 从一个进程或线程切换到另一个进程或线程.进程(有时候也称做任务)是指一个程 ...
- hdu4686矩阵快速幂
花了一个多小时终于ac了,有时候真的是需要冷静一下重新打一遍才行. 这题就是 |aod(n)| = |1 ax*bx ax*by ay*bx ...
- IOS-网络(GET请求和POST请求、HTTP通信过程、请求超时、URL转码)
// // ViewController.m // IOS_0129_HTTP请求 // // Created by ma c on 16/1/29. // Copyright © 2016年 博文科 ...
- 面筋: 奇虎360 c++ 后台开发 实习生 面试
投的是360上海的商业化部门,岗位是C++服务端开发实习生,记录一下面试历程: 视频面试,但是是有代码框让你写代码的. 一面: Q:先说一下个人信息,做过的项目 A:.......... Q:先写个翻 ...
- ansible入门01
1.批量操作 1.操作系统选型与安装: 1.安装在实体机上: 批量安装: PXE(预引导执行环境):需要网卡上有DHCP客户端去加载bootloadder cobbler: kickstack: 2. ...
- ISE创建Microblaze软核(一)
在使用FPGA时,有时会用到它做为主控芯片.对于习惯于单片机及C语言开发的人,使用FPGA做主控芯片,首先还是想到它的嵌入式软核功能.如果能够基于Microblze软核进行C语言程序的开发,相对于使用 ...
- JSP乱码(小记)
Post提交乱码: 设置请求的编码方式: request.setCharacterEncoding("utf-8"); 设置响应的编码方式: response.setCharact ...
- 用 Unity 和 HTC Vive 实现高级 VR 机制(1)
原文:Advanced VR Mechanics With Unity and the HTC Vive Part 1 作者:Eric Van de Kerckhove 译者:kmyhy VR 从来没 ...
- CS231n课程笔记翻译5:反向传播笔记
译者注:本文智能单元首发,译自斯坦福CS231n课程笔记Backprop Note,课程教师Andrej Karpathy授权翻译.本篇教程由杜客翻译完成,堃堃和巩子嘉进行校对修改.译文含公式和代码, ...
- C#读写 AB PLC 直接通过节点来读写数据 读写 AllenBradley PLC
本文将使用一个Github开源的组件库技术来读写AB PLC,使用的是基于以太网的实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作 官网:http:/ ...