解数独

力扣题目链接(opens new window)

编写一个程序,通过填充空格来解决数独问题。

一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。

示例 1:

输入:board = [
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]] 输出:[["5","3","4","6","7","8","9","1","2"],
["6","7","2","1","9","5","3","4","8"],
["1","9","8","3","4","2","5","6","7"],
["8","5","9","7","6","1","4","2","3"],
["4","2","6","8","5","3","7","9","1"],
["7","1","3","9","2","4","8","5","6"],
["9","6","1","5","3","7","2","8","4"],
["2","8","7","4","1","9","6","3","5"],
["3","4","5","2","8","6","1","7","9"]]

解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

提示:

  • 给定的数独序列只包含数字 1-9 和字符 '.' 。
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

思路

看似跟N皇后差不多,好像也可以通过一层一层的递归进行遍历,然后填满所有空格

但是区别还是挺大的(反正用同样的思路是做不出来这题的)

从数独规则的角度来捋一捋解题思路

我们需要做的是去遍历"数独棋盘",该棋盘由9个3X3的小九宫格组成

往"数独棋盘"的每个空里面填数字,要求是同行列中不能出现与之相同的数

注意,我们每次填数字的对象都是"数独棋盘",也就是说,我们在一个递归层中需要处理的是一个二维数组,这就是本题与N皇后的区别

因此,在单层递归中需要引入两个for循环,一个负责遍历行,一个负责遍历列,这样才能定位"数独棋盘"中一个待填的格子,进而判断其中有没有数、需不需要填数、填的数合不合法

代码分析

基于上述分析,还是先来三部曲

三部曲

1、确定回溯函数的参数和返回值

题目的提示说了可以假定只有一个解,意思就是说我们不需要获取数独的所有可能解

那么,碰到(或者说填完)一个符合条件的解之后我们就应该立刻返回

是不是有点熟悉,没错,这种情况时,递归函数的返回值应该设置为布尔值,并且需要接受返回结果(详见路径总和

而回溯函数的输入参数是"数独棋盘",不需要别的参数,因为本质上是去做一系列修改board的操作

class Solution {
private:
//确定回溯函数的参数和返回值
bool backtracking(vector<vector<char>>& board){
}
public:
void solveSudoku(vector<vector<char>>& board) {
}
};

2、确定终止条件

因为我们是要填满"数独棋盘"的每个格子,所以其实不需要终止,只要for循环结束就会自动终止,此时如果填完了就是找到一个结果,没填完就是当前数独无解

class Solution {
private:
//确定回溯函数的参数和返回值
bool backtracking(vector<vector<char>>& board){
//(确定终止条件,其实这里是不需要的)
}
public:
void solveSudoku(vector<vector<char>>& board) {
}
};

3、确定单层处理逻辑

这里就是本题的核心了,我们需要在这里遍历整个"数独棋盘",即遍历它的行和列

自然的,这需要使用两个for来完成

在for循环的最里层,先判断当前拿到的格子是不是空的,不是就跳过不用填,是就继续之后的逻辑

我们需要进行填数的逻辑,此时需要遍历数字1~9,判断当前通过两层for拿到的空格子应该填入哪个数字

这里是不是又很熟悉,没错,又的需要一个判断函数isVaild来对填入的数进行规则判断(详见n皇后,不过这里两者没有联系)

这部分的总体步骤如下:

​ 1、遍历行,即board里的元素

​ 2、遍历列,即board里元素的元素(二维数组里的数组元素里的数值元素)

​ 3、判断当前格子是否为空(空跳过,不空继续)

​ 4、遍历1~9看哪个数合适填入

​ ·每次遍历均使用规则判断函数进行判定

​ ·返回true就填入当前数

​ 5、触发递归,并接受递归函数的返回值,为true那本层也返回true

​ 6、回溯,结束

class Solution {
private:
//确定回溯函数的参数和返回值
bool backtracking(vector<vector<char>>& board){
//(确定终止条件,其实这里是不需要的) //确定单层处理逻辑
//两层for分别遍历行和列
for(int i = 0 ; i < board.size(); ++i){//遍历行,即board里的元素
for(int j = 0; j < board[0].size(); ++j){//遍历列,即board里元素的元素
if(board[i][j] != '.') continue;//当前格有值就跳过
//判断当前位置放1~9哪个数合适
for(char k = '1'; k <= '9'; ++k){
//判断当前格放k是否合适
if(isVaild(i, j, k, board)){//合适就放
board[i][j] = k;
if(backtracking(board)) return true;//注意了,这里是需要处理返回值的
board[i][j] = '.';
}
}
return false;//在当前空中判断1~9都不适合填入,那就是无解,直接返回false
}
}
return true;//遍历完所有格子没返回false,就说明填完了
}
public:
void solveSudoku(vector<vector<char>>& board) {
}
};

在写这部分代码时有以下注意事项:

​ 1、要是捣不清现在是行还是列,就去题目给的实例看一下(注释里也有写)

​ 2、递归时需要处理返回值

规则判断函数isVaild

判断棋盘是否合法有如下三个维度:

  • 同行是否重复
  • 同列是否重复
  • 9宫格里是否重复

这里很容易把第三种情况漏掉,请特别注意一下

	//规则判断函数,实现数独规则判定
bool isVaild(int row, int col, int curValue, vector<vector<char>>& board){
//在整个数独棋盘范围上
//判断行里是否有与curValue重复的值
for(int i = 0; i < 9; ++i){
if(board[row][i] == curValue) return false;
} //判断列里是否有与curValue重复的值
for(int j = 0; j < 9; ++j){
if(board[j][col] == curValue) return false;
} //在数独棋盘中的3X3的九宫格上
//设置每个小九宫格开始遍历的位置(每三个空格为一个小九宫格)
int miniblockRow = (row / 3) * 3;
int miniblockCol = (col / 3) * 3; //判断小九宫格里有无重复值
for(int i = miniblockRow; i < miniblockRow + 3; ++i){//注意遍历条件,一次跳3格
for(int j = miniblockCol; j < miniblockCol + 3; ++j){
if(board[i][j] == curValue) return false;
}
}
return true;
}

再刷如果有问题再补充,TBD(23:00了我顶不住了)

完整代码

class Solution {
private:
//确定回溯函数的参数和返回值
bool backtracking(vector<vector<char>>& board){
//(确定终止条件,其实这里是不需要的) //确定单层处理逻辑
//两层for分别遍历行和列
for(int i = 0 ; i < board.size(); ++i){//遍历行,即board里的元素
for(int j = 0; j < board[0].size(); ++j){//遍历列,即board里元素的元素
if(board[i][j] != '.') continue;//当前格有值就跳过
//判断当前位置放1~9哪个数合适
for(char k = '1'; k <= '9'; ++k){
//判断当前格放k是否合适
if(isVaild(i, j, k, board)){//合适就放
board[i][j] = k;
if(backtracking(board)) return true;
board[i][j] = '.';
}
}
return false;//在当前空中判断1~9都不适合填入,那就是无解,直接返回false
}
}
return true;//遍历完所有格子没返回false,就说明填完了
}
//规则判断函数,实现数独规则判定
bool isVaild(int row, int col, int curValue, vector<vector<char>>& board){
//在整个数独棋盘范围上
//判断行里是否有与curValue重复的值
for(int i = 0; i < 9; ++i){
if(board[row][i] == curValue) return false;
} //判断列里是否有与curValue重复的值
for(int j = 0; j < 9; ++j){
if(board[j][col] == curValue) return false;
} //在数独棋盘中的3X3的九宫格上
//设置每个小九宫格开始遍历的位置(每三个空格为一个小九宫格)
int miniblockRow = (row / 3) * 3;
int miniblockCol = (col / 3) * 3; //判断小九宫格里有无重复值
for(int i = miniblockRow; i < miniblockRow + 3; ++i){
for(int j = miniblockCol; j < miniblockCol + 3; ++j){
if(board[i][j] == curValue) return false;
}
}
return true;
}
public:
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
}
};

【LeetCode回溯算法#11】解数独,这次是真的用回溯法处理二维数组的更多相关文章

  1. LeetCode37 使用回溯算法实现解数独,详解剪枝优化

    本文始发于个人公众号:TechFlow,原创不易,求个关注 数独是一个老少咸宜的益智游戏,一直有很多拥趸.但是有没有想过,数独游戏是怎么创造出来的呢?当然我们可以每一关都人工设置,但是显然这工作量非常 ...

  2. java基础:进制详细介绍,进制快速转换,二维数组详解,循环嵌套应用,杨辉三角实现正倒直角正倒等腰三角,附练习案列

    1.Debug模式 1.1 什么是Debug模式 是供程序员使用的程序调试工具,它可以用于查看程序的执行流程,也可以用于追踪程序执行过程来调试程序. 1.2 Debug介绍与操作流程 如何加断点 选择 ...

  3. 【LeetCode】剑指 Offer 04. 二维数组中的查找

    二维数组查找:线性查找法 有二维数组: [  [1,   4,  7, 11, 15],  [2,   5,  8, 12, 19],  [3,   6,  9, 16, 22],  [10, 13, ...

  4. 【LeetCode回溯算法#10】图解N皇后问题(即回溯算法在二维数组中的应用)

    N皇后 力扣题目链接(opens new window) n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 给你一个整数 n ,返回所有不同的 n 皇 ...

  5. [算法][LeetCode]Search a 2D Matrix——二维数组的二分查找

    题目要求 Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the ...

  6. 剑指offer系列——二维数组中,每行从左到右递增,每列从上到下递增,设计算法找其中的一个数

    题目:二维数组中,每行从左到右递增,每列从上到下递增,设计一个算法,找其中的一个数 分析: 二维数组这里把它看作一个矩形结构,如图所示: 1 2 8 2 4 9 12 4 7 10 13 6 8 11 ...

  7. python-Day4-迭代器-yield异步处理--装饰器--斐波那契--递归--二分算法--二维数组旋转90度--正则表达式

    本节大纲 迭代器&生成器 装饰器  基本装饰器 多参数装饰器 递归 算法基础:二分查找.二维数组转换 正则表达式 常用模块学习 作业:计算器开发 实现加减乘除及拓号优先级解析 用户输入 1 - ...

  8. Java数组排序基础算法,二维数组,排序时间计算,随机数产生

    import java.util.Arrays; //包含Arrays import java.util.Random; public class HelloWorld { public static ...

  9. Arrays工具、二维数组以及LeetCode练习题

    1 Arrays PS:Arrays位于java.util包下 int binarySearch(type[] a, type key); 使用二分法查询 key 元素在 a 数组中的索引,如果数组不 ...

  10. LeetCode二维数组中的查找

    LeetCode 二维数组中的查找 题目描述 在一个 n*m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增.请完成一个搞笑的函数,输入这样的一个二维数组和一个整数,判断数 ...

随机推荐

  1. Rsync原理的学习与总结

    Rsync原理的简单学习 前言 工作这么多年, 感觉对自己帮助最大的是rsync. 用了很多rsync的脚本, 甚至因为这个脚本授权了两个专利. 但是昨天晚上在跟高手聊天时发现 自己对rsync 其实 ...

  2. JVM内存初步学习

    JVM内存初步学习   最近在学习容器内的JVM运行, 简单总结了下学习结果, 但是感觉还是分不清楚很多地方: 同事帮忙进行了 native memory的监控, 主要信息简要如下: jvm刚运行起来 ...

  3. 图片三像素问题如何解决css

    一.提出问题 在浏览器中,图片有一个下间隙问题,有人也称之为图片3像素BUG 1.这并不是什么浏览器bug,而只是英文字母书写时有个基线的问题,基线决定了图片的对其方式.这才是造成浏览器中图片下间隙的 ...

  4. 【JS 逆向百例】吾爱破解2022春节解题领红包之番外篇 Web 中级题解

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 逆向目标 本次逆向的目标来源于吾爱破解 2022 春节解题领红包之番外篇 Web 中级题,吾爱破解每年都会有派送红包活动(送吾爱 ...

  5. 从嘉手札<2024-1-17>

    昨天我以为 人生是一场体验 是一辆不会回头的列车 我们遇到了风景 感悟了风景 放下了风景 构成了自己 今天我以为 静水流深.光而不耀 可多思必多疑 思维是一种极为复杂的东西 我曾经觉得知行合一是对自我 ...

  6. 三星发布990 EVO SSD:同时支持PCIe 4.0和PCIe 5.0

    1月8日消息,三星发布了新款产品--990 EVO SSD,这是首款同时支持了PCIe 4.0 x4及PCIe 5.0 x2通道的SSD. 据了解,990 EVO面向中端市场,为2280 M.2规格, ...

  7. Docker从认识到实践再到底层原理(四-1)|Docker镜像仓库|超详细详解

    前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...

  8. 【STL源码剖析】string类模拟实现 了解底层-走进底层-掌握底层【超详细的注释和解释】

    文章目录 博主对大家的话 前言 实现过程一些要注意的点 STL中string类模拟实现 尾声 博主对大家的话 从今天开始,STL源码剖析的专栏就正式上线了!其实在很多人学习C++过程中,都是只学习一些 ...

  9. tp使用workerman消息推送

    安装 首先通过 composer 安装 composer require topthink/think-worker SocketServer 在命令行启动服务端 php think worker:s ...

  10. 使用C语言构建一个独立栈协程和共享栈协程的任务调度系统

    使用了标准库头文件 <setjmp.h>中的 setjmp 和 longjmp两个函数,构建了一个简单的查询式协作多任务系统,支持独立栈和共享栈两种任务. 其中涉及到获取和设置栈的地址操作 ...