[Lc6_记忆化搜索] 扫雷游戏 | 理解 递归vs记忆化搜索vs dp

目录

⭕1.扫雷游戏

题解

1.记忆化搜索

解法一:递归

解法二:记忆化搜索

解法三:动态规划


⭕1.扫雷游戏 (暴力+模拟)

链接:529. 扫雷游戏

让我们一起来玩扫雷游戏!

给你一个大小为 m x n 二维字符矩阵 board ,表示扫雷游戏的盘面,其中:

  • 'M' 代表一个 未挖出的 地雷,
  • 'E' 代表一个 未挖出的 空方块,
  • 'B' 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的 已挖出的 空白方块,
  • 数字'1''8')表示有多少地雷与这块 已挖出的 方块相邻,
  • 'X' 则表示一个 已挖出的 地雷。

给你一个整数数组 click ,其中 click = [clickr, clickc] 表示在所有 未挖出的 方块('M' 或者 'E')中的下一个点击位置(clickr 是行下标,clickc 是列下标)。

根据以下规则,返回相应位置被点击后对应的盘面:

  1. 如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'
  2. 如果一个 没有相邻地雷 的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的 未挖出 方块都应该被递归地揭露。
  3. 如果一个 至少与一个地雷相邻 的空方块('E')被挖出,修改它为数字('1''8' ),表示相邻地雷的数量。
  4. 如果在此次点击中,若无更多方块可被揭露,则返回盘面。


题解

这题说的很多,其实就是给一个mxn的棋盘

再给一个棋盘坐标,点击这个坐标,把修改后的棋盘返回。

我们要注意一下规则:

  • ‘M’ 代表一个 未挖出的 地雷,
  • E’ 代表一个 未挖出的 空方块,
  • B’ 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的 已挖出的 空白方块,

前面我们都只研究上下左右,这里还要考虑斜对角线4个位置。

  • 也就是说如果当前被挖的空格周围没有地雷,就把它标记成B。
  • 数字(‘1’ 到 ‘8’)表示有多少地雷与这块 已挖出的 方块相邻,
  • 数字 表示这个被挖的格子周围有多少个地雷
  • ‘X ’ 则表示一个 已挖出的 地雷。

根据以下规则,返回相应位置被点击后对应的盘面:

  • 如果一个地雷(‘M’)被挖出,游戏就结束了- 把它改为 ‘X’ 。
  • 也就是说如果刚开始给你的这个位置就是雷的话,把这个位置改成‘X’,直接结束即可!

  • 处理 E 的话,存在 两种情况:
  • 如果一个 没有相邻地雷 的空方块(‘E’)被挖出,修改它为(‘B’)
    • 并且所有和其相邻的 未挖出 方块‘E’ 都应该被递归地 揭露。
    • 如果当前被挖的位置周围没有地雷,把它改成’B‘,然后 递归 的往周围走。

  • 如果一个 至少与一个地雷相邻 的空方块(‘E’)被挖出
    • 修改它为数字(‘1’ 到 ‘8’ ),表示相邻地雷的数量。
    • 如果当前被挖的位置周围有地雷,把它修改成周围的地雷数,然后就 不要递归下去。直接返回。

如果在此次点击中,若无更多方块可被揭露,则返回盘面。

原理:

其实这就是一个模拟,已经告诉怎么去操作了。

当点击这个位置之后,我们要先统计一下点击的这个位置周围有没有地雷。

  • 周围没有地雷,就把这个位置改成’B‘,然后递归的把周围所有位置都找一遍。
  • 如果周围有地雷的话,就把这个位置改成地雷数,然后就不要从这个位置在递归下去了,返回即可。
  • 同理递归进去也是如上面一样先统计周围有没有地雷。。。。

不过这里有个细节问题,我们之前是沿着一个位置上下左右找4个位置。但是这个位置要找一圈8个位置。

  • 我们还是和之前一样,我们直接把向量数组扩展一下就可以了。
  • 可以写两个-1,1之后然后给它们分别匹配-1,1。

八邻域搜索

初始时只有M和E

  • 我们查找到E的时候进行递归
  • 标记为B和数字的时候,已经帮我们实现cheak功能啦 

E要么变为B(递归),要么变为数字(统计,不递归)

class Solution {
public:int dx[8] = {0,0,1,-1,-1,-1,1,1};int dy[8] = {1,-1,0,0,1,-1,1,-1};int m, n;vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {m = board.size();n = board[0].size();int i = click[0], j = click[1];if (board[i][j] == 'M') {board[i][j] = 'X';return board;}dfs(board, i, j);return board;}void dfs(vector<vector<char>>& board, int i, int j) {int count = 0;//先计算周围地雷数量for (int k = 0; k < 8; ++k) {int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M') {++count;}}if (count > 0) {board[i][j] = count + '0'; // 显示地雷数后停止递归} else {board[i][j] = 'B'; for (int k = 0; k < 8; ++k) {int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && board[x][y]=='E') {dfs(board, x, y); //数字停止递归//B标记以扫描,停止递归//不断对未扫描的E进行扫描}}}}
};

1.记忆化搜索

什么是记忆化搜索,下面我们以一道比较常见的 509. 斐波那契数 来演示一下什么是记忆化搜索。

  • 关于这个斐波那契数,我们很早之前就碰到过如循环、递推、递归
  • 不过它 还可以用动态规划,记忆化搜索来解决。矩阵快速幂也可以解决。

时间复杂度:

  • 循环、递推、递归,时间复杂度O(N^2)
  • 动态规划、记忆化搜索,时间复杂度O(N)
  • 矩阵快速幂,时间复杂度O(logN)

因为记忆化搜索是基于递归代码来实现的,所以我们先用递归写这道题。

解法一:递归

dfs要干的事情就是给我一个数我把它第n个斐波那契数返回来。

关于函数体怎么写,很简单求。

  • F(0) = 0,F(1) = 1,F(n) = F(n - 1) + F(n - 2),其中 n > 1。
  • 求第n个斐波那契数先把n-1和n-2斐波那契数求出来。
  • 递归出口 n<=1 返回n就可以了。
int fib(int n)
{return dfs(n);
}int dfs(int n){if(n <= 1) return n;    return dfs(n-1)+dfs(n-2);}

我们先分析一下这个递归过程。

  • 当n=5的时候,我们分析一下这个递归展开过程。
  • 就像一颗二叉树。
  • 有多少节点就要递归多少次。时间复杂度O(2^N)

我们先分析一下为什么这个递归它会这么慢。

慢其实就慢在我们会重复计算一些问题,如d(3)我们会重复进入两次,但是这两个d(3)的递归展开树是完全一样的!

这两个d(3)向上返回的值是完全一样的!

那我们想一下能不能这样优化一下,来一个备忘录

  • 就是当我从d(5)来深度优先遍历的时候,先去左边
  • 然后当我从d(3)这颗递归树返回时是一个返回值,(x为返回值)
  • 此时把d(3)=x这个f信息丢到备忘录里。以此来避免之后的重复计算

(备忘录可能是一个数组、哈希表)。

  • 然后当从左边回来然后去右边的时候,当我们又一次进入d(3)的时候,此时我就不把d(3)展开了。
  • 因为此时我去备忘录里找找我发现d(3)=x这个消息在左边就已经计算过了。
  • 所以在这里不展开了,直接在备忘录里把x给拿出来(爽了家人们),然后返回就可以了。

当有一个备忘录的时候,相同子树不在展开的时候,是不是就对递归做优化了。

像这样一种方式就叫做记忆化搜索!

解法二:记忆化搜索

在递归过程中,发现有一些 完全相同的问题

  • 我们就可以把完全相同问题的结果放到备忘录里
  • 然后递归进入相同问题的时候直接往备忘录里拿结果就可以了。
  • 这就是记忆化搜索,因此又称作带备忘录的递归。

此时你会发现其实并不止d(3)会被做优化其实就是剪枝,d(2)也会被优化。

这么多分支都不用进去。

  • 时间复杂度直接从O(2^N)变成O(N)。
  • 所以添加一个备忘录可以极大优化我们的搜索过程。
  • 这也是记忆化搜索名字的由来,带着记忆去做dfs,这些重复的地方就不要重复进去了就实现了大量的剪枝

时间复杂度从指数级别降到线性级别

如何实现记忆化搜索呢?

  • 添加一个备忘录 memo
  • 递归每次返回的时候,将结果放到备忘录里面
  • 在每次进入递归的时候,往备忘录里面瞅一瞅

开始前瞅一瞅,返回前存一存~

那添加一个什么样的备忘录呢?

紧盯这样一个原则,先找可变参数,然后将<可变参数,返回值>的映射关系存起来。

  • 在这个dfs递归函数里。可变参数就是n,返回值就是第几个斐波那契数。
  • 所以在这里仅需<int,int> 前面是第几个斐波那契数的第几,后面是存的是具体的斐波那契数。

这个备忘录搞什么数据结构呢?

  • 可以搞一个哈希表,这里我们可以来一个数组就行
  • 这个数组可以搞成全局的,也可以当成dfs参数。

有时候备忘录可能需要初始化一下。

  • 搞成全局的备忘录里都是0,但是我们备忘录里有一个规则,备忘录里面初始存的值不能跟最终结果的值是一样的。
  • 也就是说要去备忘录找这个值的时候,我得先判断一下你这个值是不是已经被计算过了
  • 如果这个备忘录里面d(3)本来就存在X值,但是我还没有进入过d(3)里呢,就可能会导致误差。
  • 所有要把备忘录初始化为这个dfs里永远都不会出现得返回值!
class Solution {
public:int memo[31];int fib(int n) {memset(memo,-1,sizeof memo);//初始化return dfs(n);}int dfs(int n){//先 备忘录里 瞅瞅if(memo[n]!=-1) return memo[n];//剪枝if(n<=1){memo[n]=n;//return 前就memo存一下return n;}memo[n]=dfs(n-1)+dfs(n-2);return memo[n]; }
};

解法三:动态规划

动态规划我们一般思路是盯着5个方向。

  • 确定状态表示
  • 推导状态转移方程
  • 初始化
  • 确定填表顺序
  • 确定返回值

解决动态规划问题现创建一个表格。可能是一维的也可能是二维的。称为dp表。

我们会赋予现赋予dp表一个含义。

如果有一个i下标,我们会赋予dp[i] 一个含义。

  • 其中这个dp[i]的含义就是状态表示。
  • 推导这个状态转移方程就我想求这个dp[i]的值的时候,我们会从前面填的表格的值来推导dp[i]是多少。
  • 相当于是避免了重复做一件事情,实现了一个从前到后有逻辑的推导,进行线性的优化计算

具体推导处理的公式就是状态转移方程。

  • 初始化就是我们填dp[i]是依赖之前填的表格,但是0这个位置状态没有办法搞。

因此必须先把0这个位置的值先初始化放后序填表。

  • 确定填表顺序 如果填dp[i]状态依赖于之前的状态,就必须是从左到右。
  • 确定返回值 根据题目要求确定最后返回的是这个表中那一个数。

其实我们可以从之前的递归和记忆化搜索直接推出这5步,因为它们是一一对应的关系。

  • dfs函数头就是给我一个数n我返回数n的斐波那契数。
  • 填写备忘录的顺序,我们是做了深度优先遍历因此会一直递归下去,所以先会把d(1),dp(0)先放到备忘录里然后在往上返回一依次放。
  • 对应dp表就是从左到右。
  • 主函数是dfs(n)调用的,对应dp表返回的就是dp[n]的值。

为什么动态规划和记忆化搜索这些都是一一对应的?

因为动态规划和记忆化搜索本质都是一样的:

  • 暴力求解(暴搜),动态规划要求dp[i]也要把前面都算出来,也是暴搜。
  • 无非就是动态规划和记忆化搜索是对暴搜的优化。

对暴力解法的优化是一样的:把已经计算过的值,存起来。 记忆化搜索算d(5),因为d(4)和d(3)已经放进备忘录里面了。

  • 直接去备忘录里找拿d(4)和d(3)就看可以。
  • 动态规划求dp[i]的时候是已经把前面dp[i-1]和dp[i-2]的值已经放到这个表里面了,然后求dp[i]的时候,直接去表里拿就就可以了。

算法导论》这本书就是把记忆化搜索和常规的动态规划 归为 动态规划。

无法就是

  • 记忆化搜索是递归(借助 OS 栈来返回)形式的 动态规划
  • 而 常规的动态规划 是一个 递推(循环) 存储的 动态规划。
class Solution {
public:int fib(int n) {//dp//方程//初始化//顺序//返回值int dp[31];dp[0]=0,dp[1]=1;for(int i=2;i<=n;i++){dp[i]=dp[i-1]+dp[i-2];}return dp[n];}
};

下面我们总结几个问题。

1.所有的递归(暴搜、深搜),都能改成记忆化搜索吗?

不是的,只有在递归过程中,出现大量完全相同的问题时,才能用记忆化搜索的方式优化。

2.带备忘录的递归、带备忘录的动态规划、记忆化搜索 都是一回事。

3.自顶向下 vs 自底向上 有什么不同

  • 无法就是解决一个问题时思考方向不同而已。
  • 自顶向下就是思考决策树时是按照从上往下的顺序来思考的,我想求d(5),我要先求出d(4)和d(3) 。。。。。
  • 自底向上就是从下往上思考,求d(5),我可以从最初开始看,由0 1 2推出 5

而这两种方式就正好对应记忆化搜索和常规动态规划。

  • 记忆化搜索是递归加一个备忘录所以记忆化搜索方式就是从上往下。
  • 常规动态规划是递推方式,先求dp[0],自下往上对推导dp[n]是多少。

4.我们在解决这个问题的时候发现了一个流程

我可以先写出暴搜,然后改成记忆化搜索,然后把记忆化搜索东西抽象处理就是动态规划。

  • 好像发现解决动态规划问题的全新流程暴搜->记忆化搜索->动态规划。
  • 以前是 常规动态规划。
  • 碰到一道动态规划的题先写成暴搜,然后改成记忆化搜索,在抽象成成动态规划。

一般这样搞也没错,但是有些题你直接写出暴搜会比你用常规动态规划成本高的多。

  • 暴搜->记忆化搜索->动态规划 和 常规动态规划 都是解决动态规划的方式。
  • 那个更好, 因人而异,因题而异。
  • 在我看来暴搜可以为我们确定状态表示,提供一个方向。

而且记忆化搜索就已经是一个动态规划了,从暴搜改到记忆化搜索时间复杂度已经是线性级别了,没有必要在搞成动态规划了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/75266.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生周刊:Kubernetes v1.33 要来了

开源项目推荐 Tekton Tekton 是一个开源的 K8s 原生 CI/CD 系统&#xff0c;它为构建、测试和部署自动化工作流提供了强大而灵活的框架。Tekton 提供了一套标准化的 API 和自定义资源&#xff08;CRDs&#xff09;&#xff0c;使得开发者能够在 K8s 集群中定义和管理 CI/CD 管…

服务新增节点、迁移笔记

文章目录 基础配置部分基础配置-hosts基础配置-jdk包准备基础配置-jdk环境变量配置基础配置-skywalking包 基础配置-apollo配置。 # 文件夹及配置基础配置-tomcat基础配置-nginx基础配置部分-磁盘挂载(这个也差点漏掉)。 防火墙部分防火墙部分-数据库及脚本防火墙部分-redis防火…

第十一章:Python PIL库-图像处理

一、PIL库简介 PIL&#xff08;Python Imaging Library&#xff09;是一个功能强大的图像处理库&#xff0c;它提供了丰富的图像处理功能&#xff0c;包括图像的打开、处理和保存等操作。PIL支持多种图像文件格式&#xff0c;如JPEG、PNG、BMP等&#xff0c;并且可以完成对图像…

【编译、链接与构建详解】Makefile 与 CMakeLists 的作用

【编译、链接与构建详解】Makefile 与 CMakeLists 的作用 前言源代码&#xff08;.c、.cpp&#xff09;编译编译的本质编辑的结果编译器&#xff08;GCC、G、NVCC 等&#xff09; 目标文件&#xff08;.o&#xff09;什么是 .o 目标文件为什么单个 .o 目标文件不能直接执行&…

Ubuntu / Debian 创建快捷方式启动提权

简述 在 Linux 系统中&#xff0c;.desktop 文件是 桌面入口文件&#xff0c;用于在桌面环境&#xff08;如 GNOME、KDE&#xff09;中定义应用程序的启动方式、图标、名称等信息。当你执行 touch idea.desktop 时&#xff0c;实际上创建了一个空的 .desktop 文件&#xff08;…

ISIS报文

IS-IS 报文 目录 IS-IS 报文 一、报文类型与功能 二、报文结构解析 三、核心功能特性 四、典型应用场景 五、抓包数据分析 六、总结 IS-IS&#xff08;中间系统到中间系统&#xff09;协议报文是用于链路状态路由协议中网络设备间交换路由信息的关键载体&#xff0c;其设…

beikeshop多商户跨境电商独立站最新版v1.6.0版本源码

一.介绍 beikeshop跨境电商独立站最新版V1.6.0源码 多商户 多商家 多语言 多币结算 本博主亲测搭建代码全开源质量相对来说很稳定的 二.服务器环境 系统&#xff1a;CentOS、 环境&#xff1a;PHP7.4 Nginx 1.21 MySQL 5.6 常见插件&#xff1a;fileinfo &#xff1b; re…

Redis批量操作详解

一、原生批量命令&#xff08;MSET&#xff09; 适用场景&#xff1a;所有键的过期时间相同或无过期设置&#xff0c;且无需条件判断。 方法&#xff1a; 将多个SET命令合并为MSET命令&#xff0c;但需要注意MSET的局限性&#xff08;无法设置过期时间&#xff0c;且所有键值对…

Spring Boot 集成实战:AI 工具如何自动生成完整微服务模块

在数字化转型的浪潮中&#xff0c;开发效率和质量是企业竞争力的关键要素。飞算 JavaAI 作为一款创新的 AI 工具&#xff0c;能在 Spring Boot 开发中&#xff0c;自动生成完整微服务模块&#xff0c;极大提升开发效率。下面&#xff0c;我们就详细介绍如何借助飞算 JavaAI&…

算法 | 2024最新算法:斑翠鸟优化算法原理,公式,应用,算法改进研究综述,matlab代码

基于斑翠鸟优化算法的原理、应用及改进研究综述 一、算法原理 斑翠鸟优化算法(Pied Kingfisher Optimizer, PKO)是2024年由Bouaouda等人提出的一种新型仿生智能优化算法,其灵感来源于斑翠鸟的捕食行为与共生关系。算法通过模拟斑翠鸟的栖息悬停、潜水捕鱼及与其他生物的共生…

RabbitMQ高级特性--重试特性

目录 1.重试配置 2.配置交换机&队列 3.发送消息 4.消费消息 5. 运行程序观察结果 6. 手动确认 注意&#xff1a; 在消息传递过程中, 可能会遇到各种问题, 如网络故障, 服务不可用, 资源不足等, 这些问题可能导致消息处理失败. 为了解决这些问题, RabbitMQ 提供了重试机制, …

Vue 组件通信 - 中央事件总线

Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue组件通信 - 中央事件总线 目录 中央事件总线 图示 准备工作 设置页面元素 创建组件 总结 中央事件总线 使用vue的监听和触发来实现中央事件总线方式。 on监听 emit触发&#xff0c;组件按钮绑定点击事件&#xff0c…

5.0 WPF的基础介绍1-Grid,Stack,button

WPF: Window Presentation Foundation. WPF与WinForms的对比如下&#xff1a; 特性WinFormsWPF技术基础基于传统的GDI&#xff08;图形设备接口&#xff09;基于DirectX&#xff0c;支持硬件加速的矢量渲染UI设计方式拖拽控件事件驱动代码&#xff08;简单但局限&#xff09;…

QT软件设计可考虑回答

在Qt应用中是否引入抽象类需要根据具体场景权衡&#xff0c;以下是分层建议&#xff1a; 建议采用抽象类的3个典型场景&#xff1a; 传感器系统抽象&#xff08;强推荐&#xff09; class AbstractSensor { public:virtual ~AbstractSensor() default;virtual QVector<L…

pytorch学习(b站小土堆学习)

1 环境配置 参考链接 2. dir 和 help函数 dir()&#xff1a;用于查看某一模块函数的方法 help()&#xff1a; 用于查看某方法的使用方法 3. dataset类实战 利用Image对象打开图片&#xff0c;利用os模块的地址拼接组成图片路径 当我们用方括号访问元素对象时&#xff0c;…

Unity TextMeshPro 实现文本逐字淡出效果

Unity TextMeshPro 实现文本逐字淡出效果 前言项目思路场景布置代码编写 前言 在处理角色对话时经常会用到一些文本动画&#xff0c;正好记录一下。使用 TextMeshPro&#xff0c;我们可以直接操作文本的顶点数据&#xff0c;实现诸如渐变、动画等效果&#xff0c;为游戏界面和…

Mathtype无法插入到Word中

在word工具栏上有没有出现Mtahtype&#xff0c;会出现以下两种情况&#xff1a; 1. 没有出现Mtahtype 2. 出现Mtahtype&#xff0c;但是点击会出现弹窗 “ Couldnt find the MathPage.wll ” 解决方案 首先查看word版本是32位还是64位&#xff0c;这个位数是office安装位数…

责任链模式_行为型_GOF23

责任链模式 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;核心思想是将多个处理请求的对象连成一条链&#xff0c;请求沿链传递直到被处理。它像现实中的“多级审批流程”——请假或报销时&#xff0c;申请会逐级提交给…

Qt图形化界面为何总被“冷落“?

在Qt开发者的IDE中&#xff0c;Qt Designer总像一个被遗忘的角落——即便它有着直观的拖拽式界面设计功能。通过分析GitHub上超过5000个Qt项目发现&#xff0c;仅有17%的项目使用.ui文件构建界面。这个数据背后&#xff0c;隐藏着开发者群体对GUI构建方式的集体选择。我们不禁要…

SQL Server从安装到入门一文掌握应用能力。

本篇文章主要讲解,SQL Server的安装教程及入门使用的基础知识,通过本篇文章你可以快速掌握SQL Server的建库、建表、增加、查询、删除、修改等基本数据库操作能力。 作者:任聪聪 日期:2025年3月31日 一、SQL Server 介绍: SQL Server 是微软旗下的一款主流且优质的数据库…