写在前面
博客来源:翻译自youtube高赞技术视频,并精加工和细化。
适合阅读:想要搞懂动态规划的小伙伴~
动态规划是一项杰出的计算技术,它既保留了穷举法的精确性,又吸收了贪心算法的高效率。
它主要应用于两个领域:
- 一是寻找问题的最优解,比如计算某个问题的最小值或最大值;
- 二是计算问题可能的解的总数。
尽管动态规划能够处理的问题类型远不止这些,但这两种情况是最典型的。
动态规划的起源
动态规划技术由理查德·贝尔曼在20世纪50年代提出,其名称虽然听起来有些奇特,但背后有着一段趣闻——贝尔曼为了掩盖自己正在进行的数学研究,特意选用了这样一个含糊的术语,以此来规避当时对“研究”一词抱有极度恐惧和厌恶情绪的华盛顿国防部长。

面试题中很常见
动态规划在众多面试题中频繁出现,常被视为一个颇具挑战性的主题。然而,一旦你掌握了它的基础原理并解决了一定数量的问题,你将变得擅长解决这类问题,甚至可能爱上它们。
动态规划的精髓在于,识别并解决所有子问题(如下图所展示)。虽然总体思路并不复杂,但有时识别出需要解决的具体问题却颇具难度。
幸运的是,人类大脑擅长模式识别,因此,随着解决的问题数量增加,我们会越来越快地识别出需要解决的子问题。
在本文,我们将从基础出发,探讨四个问题。
- 斐波那契数列问题:如何高效地计算第n个斐波那契数,避免递归方法中的重复计算问题。
- 最少硬币问题:给定不同面额的硬币和目标金额,如何找出组成目标金额所需的最少硬币数量。
- 硬币组合问题:不同于最少硬币问题,这个变体要求计算组成给定总和的不同硬币组合方式的总数。
- 兔子在网格中的路径问题:在一个N乘以M的网格中,兔子只能向下或向右移动,需要计算兔子从左上角到右下角的所有可能路径数量。
下一篇博客,我们还会进一步深入探讨更多的动态规划问题,请大家关注/点赞/收藏一下本博客,谢谢。
动态规划基础:斐波那契数列
让我们从一个经典的斐波那契数列问题开始。假如,我们希望编写一个名为Fib的函数,它能够返回第n个斐波那契数。
斐波那契数列的前两个数字都是1,每个后续数字都是前两个数字的和。
例如,第六个斐波那契数是8,它是前两个数字3和5的和。
我们首先尝试用递归的方法来解决这个问题。在递归的逻辑中,我们定义了两个基本情况:
情况1:当n为1或2时,函数直接返回1。
情况2:对于其他的n值,函数则返回第n-1个和第n-2个斐波那契数的和。
当我们尝试计算第七个斐波那契数时,结果是13,看起来一切正常。然而,一旦我们尝试计算第50个斐波那契数,程序的运行速度就会变得极其缓慢。因此,这种方法虽然简洁,但它的性能却存在明显的问题。
递归问题
让我们通过可视化这个函数的执行过程,来理解其中的原因。
要计算Fib(5),我们需要先计算Fib(4)。要计算Fib(4),我们又需要计算Fib(3)。
要计算Fib(3),我们又需要计算Fib(2)。最后,由于Fib(2)是基本情况之一,所以返回1。
我们继续计算Fib(3),这需要Fib(1),那也是1。最终,我们可以说Fib(3)是2。
我们回到计算Fib(4),现在我们再次需要Fib(2),我们知道这是1,所以Fib(4)现在是2+1=3。
现在我们回到计算Fib(5),我们需要知道Fib(3)。要计算Fib(3),我们再次经历相同的过程。所以我们需要Fib(2)和Fib(1)。
现在,我们可以将这些加起来得到2。最后,我们可以计算Fib(5),结果是5。
正如你看到的,这是一个非常漫长的过程,为了得到Fib(5)的结果,需要大量的迭代。
你能告诉我这个函数的时间复杂度是多少吗?
时间复杂度
在讨论这个函数的时间复杂度时,我们可以假设所处理的数字相对较小,这样可以使得加法操作的执行速度非常快。
这个假设的目的是为了简化分析,因为我们主要关注的是算法在输入规模增加时的性能表现,而不是具体数字的大小对运行时间的影响。
假设运行时间是T(n),即T(n-1)加上T(n-2)加上O(1)。