【递归,搜索与回溯算法 & 记忆化搜索】深入理解记忆化搜索算法:记忆化搜索算法小专题

【递归,搜索与回溯算法 & 记忆化搜索】深入理解记忆化搜索算法:记忆化搜索算法小专题

    

  


前言:实现记忆化搜索的一般步骤 

    (1) 实现记忆化搜索代码步骤    



    (2) 如何将暴搜代码转换成记忆化搜索代码?    



    (3)如何添加一个备忘录?        


斐波那契数


    题目解析    



    算法原理    


    解法一:递归      



 时间复杂度高是因为递归展开树有很多次重复计算,我们可以优化这些重复的计算;我们可以创建一个备忘录,当计算其中一个分支时,把计算出的 d(i) 放入一个"备忘录"中 ( i = 1 ....... n ),当递归其他分支时,我们通过备忘录存储好的计算结果,减少递归树额外重复的展开;


    解法二:记忆化搜索   


当我们在递归的时候,发现递归过程会重复进行完全相同的问题,我们就把这些完全相同的问题存储到额外创建的"备忘录"中,再后续递归出现相同问题,直接从备忘录中拿计算好的结果即可,避免不必要的重复递归; 

所以记忆化搜索,就是一个带备忘录的递归;记忆化搜索,其实也是剪枝的一种方式,在本题使用记忆化搜索,就能把指数级别的时间复杂度降到常数级别;

本题我们存的可变参数和返回值的映射是< index , memory[ index ] >,表示< n , fib (n) >;


    编写代码       



    解法三:动态规划   


动态规划简单介绍

我们可以通过递归,来推出上面动态规划的五步,因为它们是一 一对应的关系 ;


    常规的动态规划&记忆化搜索的本质     




    编写代码       



    拓展问题     



不同路径


    题目解析    



为了避免处理过多边界情况,我们从下标1开始计数: 


    算法原理    


    解法:记忆化搜索    



    1. 写出暴搜代码    


    函数头   

我们直接定义 dfs(int i , int j),给 dfs() 的任务:表示给一个坐标,通过递归求出,到达( i , j ) 坐标一共有多少种方法;


    函数体     


如果机器人想要到达一个位置,我们只需要关心到达这个目标位置的前一个位置即可,而前一个位置有两种情况:

如果我们知道:到达上面的圆圈有多少种方式,那么我们直接走到目标位置即可;


到达目标位置的路径数 = 目标位置的前一个位置的所有情况对应路径数之和;

dfs( i , j ) = dfs ( i - 1 , j ) + dfs ( i , j - 1 ) ( i > 0 && j > 0)


    递归出口    


目标位置是 ( 1 , 1 ),dfs( 1 , 1 ) = dfs ( 0 , 1 ) + dfs ( 1 , 0 ) ,此时就会出现越界情况;

所以递归出口 if ( i == 0 || j == 0 ) return 0 ,表示能到达 ( 0 , j ) 或者 ( i , 0 ) 的路径数之和为0; 

还有一个递归出口,if ( i ==1 && j == 1 ) return 1,表示位于起点,路径数之和为 1



    编写代码     



    暴搜转换成记忆化搜索      



    发现重复问题     



    添加备忘录    



    编写代码       


    处理细节问题    




     修改成动态规划代码    



最长递增子序列


    题目解析    



    算法原理    


我们以这个例子进行讲解 


     递归     


 对于本题,要找出最长递增子序列,我们就可以先暴力查找所有递增子序列,在这些递增的子序列中,找到长度最长的子序列即可;


    函数头   


我们要找的是最长递增子序列的长度,所以 dfs 的任务:给一个起点,后续找出以这个起点的最长递增子序列长度;int dfs( int pos ) ,返回以 pos 为起点的最长递增子序列长度;


    函数体    


函数体会找以 pos 为起点之后的递增子序列的最大值 ,最大值+1 就是最终的返回值;


    递归出口     


本题不需要递归出口,因为我们使用的循环不会越界; 


    编写代码        


这个 dfs 其实写出来要考虑很多东西,所以暴搜 -> 记忆化搜索 -> 动态规划这条思考路径也没有那么好用;


     暴搜修改为记忆化搜索    


    重复子问题    



    编写代码       


 

这道题使用记忆化搜索的时间复杂度高,是因为这道题的最优解是贪心算法; 


    细节问题    



    动态规划    


    注意填表顺序    



我们这里解释一下填表顺序为什么是从后往前,我们先来看记忆化搜索的 dfs 函数体:

求 dfs( pos ) 时,是依赖 dfs( pos+1 ) 以及往后位置的值,因此我们在填 dp 表的 dp[ pos ],也需要依赖 dp[ pos+1 ] 以及 pos +1 后面的值,所以后面的值要先算出来;


    解释状态表示 dp[ i ]     


第二层循环用于遍历以 i+1 为起点后面的所有序列元素, 并且把最长递增子序列长度填入对应 dp 表中:


 此时 dp[ i ] 存的是以 i 为起点的最长递增子序列的长度;

所以如果 nums[ i ] 是最大的元素,那么dp[ i ] =1;



    注意返回值     


我们填表的时候, dp[ i ] 记录的是以 i 这个位置的最长递增子序列的值和 i + 1 位置最长递增子序列的最大值; 

但是返回值不是 dp 表某一个元素,而是 dp 表中所有元素的最大值;



猜数字大小Ⅱ


    题目解析    



    算法原理    


假设给的 n = 10,那么如果我们采用二分查找策略:


虽然能在一般情况下最快速地找到要猜到的数,但是我们保证赢的情况下(采取的决策面临的最坏情况),至少需要花的钱为决策树最长分支各个节点的总和:


所以对于这题,二分查找策略并不是最优解,最优解:


    暴搜    



 我们要的是所有情况下的 i 拿到的所有最大值分支中,最小的那条分支( 合照最后一排最矮的人);

    处理细节问题    




    编写代码        



     暴搜修改为记忆化搜索    


    重复子问题    



    添加备忘录     



    编写代码       



矩阵中的最长递增路径


    题目解析    



    算法原理    


以矩阵的每一个位置为起点进行暴搜,找出每个起点所有的递增路径,返回递增路径的最大值; 

然后最终返回所有起点最长递增路径的最大值即可;


    解法:记忆化搜索   


这里可以使用记忆化搜索,是因为当我们以某一个位置为起点时,这个位置的最长递增路径是固定的,我们把这个位置的最长递增路径存在备忘录中;

后续以别的位置为起点,递归到这个位置时,直接向上返回这个位置对应备忘录的元素即可;


    处理细节问题    


    找递增路径决定了不需要设置标记数组     



把 len 设置为参数(而不是全局遍历),表示当调用新的 dfs ,把 len 置为1,1 代表已经算上这个位置的方格;

我们每次递归都要找新的 ( x , y ) 的所有递增路径,并且让 len 等于这些路径的最大值;


 我们解释一下为什么要拿 len 和 dfs( x , y ) + 1 进行比较:


    编写代码       


    暴搜    

 


    记忆化搜索    



【递归,搜索与回溯】 专题完结啦! 撒花 !😊😊💖💖🎈🎉🎉🎉

 

Read more

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk

Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎 在鸿蒙(OpenHarmony)系统的端云一体化登录、政企应用的安全审计或复杂的跨端权限校验场景中,如何确保来自云端授信中心的 JWT Token 既能被正确解析(Decode),又能被严密地校验其合法性与过期时间?jwt_io 为开发者提供了一套工业级的、基于 RFC 7519 标准的 JSON Web Token 深度处理方案。本文将深入实战其在鸿蒙应用安全底座中的应用。 前言 什么是 JWT IO?它不仅是一个简单的 Base64 解码器,而是一个具备深厚 RFC

By Ne0inhk
解决 Android WebView 无法加载 H5 页面常见问题的实用指南

解决 Android WebView 无法加载 H5 页面常见问题的实用指南

目录 1. WebView 简介 2. 常见问题 3. 网络权限设置 4. 启用 JavaScript 5. DOM Storage 的重要性 6. 处理 HTTPS 问题 7. 设置 WebViewClient 8. 调试工具 9. 其他调试技巧 10. 结论 相关推荐 1. WebView 简介         Android WebView 是一种视图组件,使得 Android 应用能够显示网页内容。它基于 Chromium,具备现代浏览器的许多功能,包括支持 HTML5、CSS3 和 JavaScript。这使得 WebView 成为展示在线内容和混合应用开发的理想选择。 2.

By Ne0inhk
【前端实战】Axios 错误处理的设计与进阶封装,实现网络层面的数据与状态解耦

【前端实战】Axios 错误处理的设计与进阶封装,实现网络层面的数据与状态解耦

目录 【前端实战】Axios 错误处理的设计与进阶封装,实现网络层面的数据与状态解耦 一、为什么网络错误处理一定要下沉到 Axios 层 二、Axios 拦截器 interceptors 1、拦截器的基础应用 2、错误分级和策略映射的设计 3、错误对象标准化 三、结语         作者:watermelo37         ZEEKLOG优质创作者、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、火山KOL、支付宝合作作者,全平台博客昵称watermelo37。         一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。 --------------------------------------------------------------------- 温柔地对待温柔的人,包容的三观就是最大的温柔。 --------------------------------------------------------------------- 【前

By Ne0inhk