动态规划面试宝典
卢誉声
Autodesk 首席工程师
9629 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 23 讲
动态规划面试宝典
15
15
1.0x
00:00/00:00
登录|注册

03 | 备忘录:如何避免递归中的重复计算?

你好,我是卢誉声。
从前面的课程中我们已经看到,动态规划问题的一般形式就是求最值。因此我先讲解了什么是最优解问题,在考虑整体最优的情况下,我们需要找到一种办法获取最优解。那么最简单直接的做法是什么呢?
其实就是把所有可行的答案穷举出来,然后在所有可行的答案中找出满足条件的最值。
这样的解法看似“天衣无缝”,但它有着重要的缺陷,而且这个缺陷是我们在面试过程中需要极力避免的:它的执行效率极低。
导致这个问题的罪魁祸首是重叠子问题,我已经不止一次提到这个概念了。那么你该如何解决重叠子问题并提高算法效率呢?
接下来我们就带着这个问题,开始学习今天的内容:备忘录。看看它能否有效解决递归过程中出现的大量重复计算的问题,提高算法效率。

什么是重叠子问题?

斐波那契数列没有求最值的问题,因此严格来说它不是最优解问题,当然也就不是动态规划问题。但它能帮助你理解什么是重叠子问题。首先,它的数学形式即递归表达是这样的:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了递归中的重复计算问题及其解决方法——备忘录的应用。作者以斐波那契数列和硬币找零问题为例,说明了递归中重复计算的问题所在,并通过具体的代码实现和图示清晰地展示了如何使用备忘录来解决这一问题。文章还总结了重叠子问题处理模式和重叠子问题缓存的限制,为读者提供了解决指数级时间复杂度问题的重要思路。通过本文的阐述,读者可以快速了解递归中的重复计算问题及备忘录的应用,为动态规划问题的解决提供了重要思路。备忘录的思想极为重要,特别是当求解的问题包含重叠子问题时,只要面试的问题包含重复计算,你就应该考虑使用备忘录来对算法时间复杂度进行简化。含有备忘录的递归算法已经与动态规划思想十分相似了,从效率上说也是如此。备忘录让我们实现了对算法时间复杂度的“降维打击”,这与贪心算法到递归的进步程度不同,这是真正意义上的动态规划思维。记住使用备忘录来优化你的算法时间复杂度,它是提高算法效率的高级手段。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《动态规划面试宝典》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • webmin
    递归是分治处理问题的方法分为两部分:递和归,递是自上而下,分解问题,归是自下而上收集计算处理结果。如果要反过来就会变成先收集计算结果后分解问题在逻辑上是矛盾的。 另把递归改为迭代方式,也是在用stack或queue等模拟压栈和出栈,用在堆上分配内存的方式解决栈大小限制的问题,本质还是自上而下的。

    作者回复: 没错。递归算法始终是自上而下的,这是由其算法特性决定的。

    2020-09-19
    2
    28
  • osun
    递归只能自顶向下,不能自底向上,如果要实现可以借助栈数据结构

    作者回复: 对~

    2020-09-18
    6
  • 追风筝的人
    F(x)=H(G(V(F(S(x,c)),c)) 好像每个问题都能用函数表达,函数即正义

    作者回复: 此刻,我特别想把开篇词那张图改一改,放在这!函数即正义~

    2020-10-19
    4
  • pearl刘东洋
    F(x)=H(G(V(F(S(x,c)),c))这个函数是不是少了一个右括号?

    作者回复: 火眼金睛!是的,已纠正。

    2021-04-13
    2
  • 3.141516
    递归等价于 f(n) 和 f(n - 1) 之间的方程描述,即要想知道 n 需要知道 n - 1,在逻辑上是不可逆的。哪怕你写出了 f(n - 1) 和 f(n) 的方程描述,前提是题目编程知道 n,求解 1,所以实际上还是一种自上而下的。

    作者回复: 动态规划其实是通过自下而上解决递归问题的重复计算问题,你从整个问题结构去看肯定还是自上而下的,动态规划只不过是调整了计算顺序。

    2021-05-10
    1
  • 宋不肥
    有了缓存,其实就是在最末尾位置上找可以往前一步范围内的最优质值,其实就是一步范围的贪心,但前序的所有缓存值都是前序的全局最优解了,所以一步贪心就是全局最优解!所以这样倒推的贪心就是动态规划,由已有的全局最优解贪心到最后所求全局的最优解,是一种递推的方式,所以wiki上dp的翻译其实是利用分治的动态递推!分治这里其实就是贪心!利用递归实现而已!所以加上回溯的贪心递推就是DP!终于自己把逻辑理顺了!比较难的好课还是得二刷三刷才能完全理解熬!

    作者回复: 应该这样说,DP里肯定是用到了和贪心类似的“根据当前情况“求解”下一步的最优解“的思路。只不过DP是在分析问题的性质和结构后定义了一个正确的”贪心“函数,确保每一步的贪心(局部最优解)得到的就是这种状况下的全局最优解,只不过求解顺序肯定是自下向上而已。同时又利用缓存避免重复计算。

    2020-10-28
    1
  • Paul Shan
    老师能否举一个关于下面描述的例子,多谢! 有些问题虽然看起来像包含“重叠子问题”的子问题,但是这类子问题可能具有后效性,但我们追求的是无后效性。所谓无后效性,指的是在通过 A 阶段的子问题推导 B 阶段的子问题的时候,我们不需要回过头去再根据 B 阶段的子问题重新推导 A 阶段的子问题,即子问题之间的依赖是单向性的。

    作者回复: 理解非常正确!赞

    2020-09-24
    2
    1
  • 无隅
    这个f(n)的分解树图要是能横过来就好啦(不过我怀疑是不是横过来放不下 ,所以展示成上下形式)

    作者回复: 嗯嗯,对的~

    2022-08-10归属地:上海
  • 全麦小面包
    先考虑子问题的个数,我只画了这颗树的一部分,因此从树上这个比较难看出来,但从斐波那契数列的题目上我们可以推广,得到其个数是 O(nm), m=|values|,即指数级别;再考虑求解一个子问题的复杂度:每个子问题中含有一个循环,因此时间复杂度为 O(m), m=|values|;综上所述,该算法的时间复杂度是 O(mnm), m=|values|。 这里的mn是什么?n不能简单地用总金额替代吧?否则n=10000,m={1000,5000},这个时间复杂度不是爆炸?最好能明确地指出它们的含义,感谢!

    作者回复: 这里m是总额的数量,n是面值的数量。时间复杂度是做规模估计,所以实际上不会有那么多次(每次从0遍历到m),但是这个是不影响时间复杂度的规模的(这个维度上只会有常数级别的缩减),的确是“爆炸”。

    2021-09-01
  • 空想家
    min(f(x−c)+1),x>0,f(x−c)​=−1,c∈C0,x=0−1,x<0​ 这个怎么理解呢

    作者回复: x是总额,C是面值的集合,c表示从C集合中取面值,min(f(x-c)+1)表示从C中取出所有的c,然后取f(x-c)+1的最小值作为f(x)的值。

    2021-08-25
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部