当前位置: 首页 > news >正文

回溯算法37:解数独

主要是我自己刷题的一些记录过程。如果有错可以指出哦,大家一起进步。
转载代码随想录
原文链接:
代码随想录
leetcode链接:37. 解数独

题目:

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

数独的解法需 遵循如下规则:

数字 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”]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
在这里插入图片描述

提示:

board.length == 9
board[i].length == 9
board[i][j] 是一位数字或者 '.'
题目数据 保证 输入数独仅有一个解

思路:

棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是二维递归

怎么做二维递归呢?

N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。

本题就不一样了,本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深

因为这个树形结构太大了,我抽取一部分,如图所示:
在这里插入图片描述

回溯三部曲

递归函数以及参数

递归函数的返回值需要是bool类型,为什么呢

因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。

代码如下:

bool backtracking(vector<vector<char>>& board)

递归终止条件

本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。

不用终止条件会不会死循环

递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!

那么有没有永远填不满的情况呢

这个问题我在递归单层搜索逻辑里再来讲!

递归单层搜索逻辑

在这里插入图片描述在树形图中可以看出我们需要的是一个二维的递归(也就是两个for循环嵌套着递归)

一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性

代码如下:(详细看注释)

bool backtracking(vector<vector<char>>& board) {
    for (int i = 0; i < board.size(); i++) {        // 遍历行
        for (int j = 0; j < board[0].size(); j++) { // 遍历列
            if (board[i][j] != '.') continue;
            for (char k = '1'; k <= '9'; k++) {     // (i, j) 这个位置放k是否合适
                if (isValid(i, j, k, board)) {
                    board[i][j] = k;                // 放置k
                    if (backtracking(board)) return true; // 如果找到合适一组立刻返回
                    board[i][j] = '.';              // 回溯,撤销k
                }
            }
            return false;                           // 9个数都试完了,都不行,那么就返回false
        }
    }
    return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}

注意这里return false的地方,这里放return false 是有讲究的

因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!

那么会直接返回, 这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去

判断棋盘是否合法

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

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

代码如下:

bool isValid(int row, int col, char val, vector<vector<char>>& board) {
    for (int i = 0; i < 9; i++) { // 判断行里是否重复
        if (board[row][i] == val) {
            return false;
        }
    }
    for (int j = 0; j < 9; j++) { // 判断列里是否重复
        if (board[j][col] == val) {
            return false;
        }
    }
    int startRow = (row / 3) * 3;
    int startCol = (col / 3) * 3;
    for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
        for (int j = startCol; j < startCol + 3; j++) {
            if (board[i][j] == val ) {
                return false;
            }
        }
    }
    return true;
}

最后整体C++代码如下:

class Solution {
private:
bool backtracking(vector<vector<char>>& board) {
    for (int i = 0; i < board.size(); i++) {        // 遍历行
        for (int j = 0; j < board[0].size(); j++) { // 遍历列
            if (board[i][j] == '.') {
                for (char k = '1'; k <= '9'; k++) {     // (i, j) 这个位置放k是否合适
                    if (isValid(i, j, k, board)) {
                        board[i][j] = k;                // 放置k
                        if (backtracking(board)) return true; // 如果找到合适一组立刻返回
                        board[i][j] = '.';              // 回溯,撤销k
                    }
                }
                return false;  // 9个数都试完了,都不行,那么就返回false
            }
        }
    }
    return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
bool isValid(int row, int col, char val, vector<vector<char>>& board) {
    for (int i = 0; i < 9; i++) { // 判断行里是否重复
        if (board[row][i] == val) {
            return false;
        }
    }
    for (int j = 0; j < 9; j++) { // 判断列里是否重复
        if (board[j][col] == val) {
            return false;
        }
    }
    int startRow = (row / 3) * 3;
    int startCol = (col / 3) * 3;
    for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
        for (int j = startCol; j < startCol + 3; j++) {
            if (board[i][j] == val ) {
                return false;
            }
        }
    }
    return true;
}
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

总结

解数独可以说是非常难的题目了,如果还一直停留在单层递归的逻辑中,这道题目可以让大家瞬间崩溃。

所以我在开篇就提到了二维递归,这也是我自创词汇,希望可以帮助大家理解解数独的搜索过程。

一波分析之后,再看代码会发现其实也不难,唯一难点就是理解二维递归的思维逻辑。

自己的代码

class Solution {

    bool isUsed(int row, int col, char k, vector<vector<char>>& board) {
        for (int i = 0; i < 9; i++) {     //该列的后面(遍历这行)
            if (board[row][i] == k) return true;
        }

        for (int i = 0; i < 9; i++) {     //(遍历这列)
            if (board[i][col] == k) return true;
        }

        int startRowIndex = (row / 3) * 3;//行的起始下标
        int startColIndex = (col / 3) * 3;//列的起始下标

        for (int i = startRowIndex; i < startRowIndex + 3; i++) {
            for (int j = startColIndex; j < startColIndex + 3; j++) {
                if (board[i][j] == k)return true;
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>& board, int row) {
        /*vector<vector<char>> temp = board;*/
        if (row == 9)   return true;

        for (int col = 0; col < 9; col++) {           //遍历该行的这些列
            if (board[row][col]=='.') {           //这个列是空的需要填字。
                for (char k = '1'; k <= '9'; k++) {  //该空格尝试1-9.
                    if (isUsed(row, col, k, board))  continue; //这个数字在遍历过的列中已经填过了或者不满足棋盘条件。
                    board[row][col] =  k;    //(row,col)位置填数字k.
                    bool result = dfs(board, row);
                    if (result) return true;
                    board[row][col] = '.';    //(row,i)位置做檫除
                }
                return false;
            }
        }
        return dfs(board, row + 1);
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        dfs(board, 0);
    }
};

相关文章:

  • Web前端 jQuery
  • 银行数字化转型导师坚鹏:银行产品经理技能快速提升之道
  • aws beanstalk 实例日志和环境状态日志的轮换和流式传输配置
  • GameFramework框架详解之 Scene场景
  • ChatGPT 使用 拓展资料:如何处理OpenAI 对 API 的调用限速
  • SoapUI基础使用教程
  • 【MySQL】表的完整性约束——非外键约束
  • vue打包上线利用插件去除 console
  • Spring bean的创建过程
  • [架构之路-148]-《软考-系统分析师》- 7-企业信息化战略与实施-5-企业信息系统、电子政务
  • 机器学习笔记之策略
  • 面向对象练习题(6)
  • 基于springboot实现时间管理系统演示【附项目源码+论文说明】
  • 【致敬嵌入式攻城狮第2期活动预热征文】——蜂鸣器(内化)
  • 第8章_索引的创建与设计原则
  • [译] 怎样写一个基础的编译器
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • ES6简单总结(搭配简单的讲解和小案例)
  • GraphQL学习过程应该是这样的
  • JavaScript异步流程控制的前世今生
  • Java-详解HashMap
  • Java应用性能调优
  • linux安装openssl、swoole等扩展的具体步骤
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • PAT A1120
  • Travix是如何部署应用程序到Kubernetes上的
  • Vue官网教程学习过程中值得记录的一些事情
  • windows下mongoDB的环境配置
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 模型微调
  • 前端面试总结(at, md)
  • 思考 CSS 架构
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 一文看透浏览器架构
  • 用mpvue开发微信小程序
  • 自动记录MySQL慢查询快照脚本
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​2021半年盘点,不想你错过的重磅新书
  • ​你们这样子,耽误我的工作进度怎么办?
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (C++17) std算法之执行策略 execution
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (SpringBoot)第七章:SpringBoot日志文件
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • (转)视频码率,帧率和分辨率的联系与区别
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .Net core 6.0 升8.0