解锁动态规划:从斐波那契到高效算法

简介: 解锁动态规划:从斐波那契到高效算法

👤作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

动态规划(Dynamic Programming, DP)是解决优化问题的一种算法策略,它将一个复杂问题分解为更小的子问题,通过解决子问题来逐步找到复杂问题的最优解。动态规划适用于有重叠子问题和最优子结构性质的问题。接下来,我们通过一个经典的动态规划问题——斐波那契数列(Fibonacci Sequence)来详细介绍动态规划的思路和实现步骤。

问题定义

斐波那契数列是这样一个序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …,其中每个数字(从第三个数字起)都是前两个数字的和。斐波那契数列的定义如下:

我们的目标是编写一个函数,输入n,输出斐波那契数列的第n项。

1. 递归解法(非DP解)

首先,我们尝试使用递归解法,这种方法简单直观,但效率较低。

def fibonacci_recursive(n):
    if n <= 1:
        return n
    else:
        return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)

这个递归解法虽然简单,但它进行了大量重复计算,时间复杂度为O(2^n)。

我们可以通过分析斐波那契数列的递归树来直观地看出重复计算的发生。

斐波那契数列的递归树

考虑计算F(5)的过程,递归树如下所示:

在这个树状结构中,每个节点表示一个递归调用,节点的值是调用的参数。从这个树中我们可以观察到:

  1. 重复的子问题F(3)F(2)F(1)F(0)被计算了多次。特别是F(2),在整个计算过程中被重复计算了三次。
  2. 增长的递归调用:随着参数n的增加,递归调用的数量呈指数级增长。例如,F(n)的计算会导致F(n-1)F(n-2)的计算,而这些调用又会分别导致更多的递归调用。
分析重复计算的原因

重复计算的主要原因在于,递归解法中对每个子问题的解决都是独立进行的,它没有考虑到在求解过程中,很多子问题实际上是相同的。由于递归方法没有记录已经解决的子问题的结果,每次遇到这些子问题时,它都会重新进行计算。

2. 动态规划解法

为了提高效率,我们使用动态规划的方法,避免重复计算。

步骤1:定义状态

定义dp数组,其中dp[i]表示斐波那契数列中第i个数的值。

步骤2:确定状态转移方程

根据斐波那契数列的定义,我们可以得到状态转移方程:dp[i] = dp[i-1] + dp[i-2]

步骤3:确定初始条件和边界条件

dp[0] = 0, dp[1] = 1

步骤4:计算顺序

从小到大计算dp数组的值。

动态规划代码实现
def fibonacci_dp(n):
    if n <= 1:
        return n
    dp = [0] * (n+1)
    dp[1] = 1
    for i in range(2, n+1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

这种方法的时间复杂度为O(n),空间复杂度也为O(n)。

3. 动态规划优化

对于斐波那契数列问题,我们实际上不需要保存整个dp数组,只需要保存前两个状态即可。

优化后的代码
def fibonacci_dp_optimized(n):
    if n <= 1:
        return n
    prev, curr = 0, 1
    for i in range(2, n+1):
        prev, curr = curr, prev + curr
    return curr

这个优化后的版本将空间复杂度降低到了O(1)。

4. 算法思考

1. 子问题

在动态规划中,我们将原问题分解为较小的、相互关联的子问题。对于斐波那契数列问题,求F(n)可以看作是一个原问题,它可以分解为求F(n-1)F(n-2)两个子问题。而F(n-1)F(n-2)又可以继续分解,直到F(1)F(0)

2. 重叠子问题

动态规划适用于那些有大量重叠子问题的情况,即不同的问题求解路径中包含了许多相同的子问题。在递归求解斐波那契数列时,F(n-1)F(n-2)会重复计算F(n-3)F(n-4)等更小的子问题。动态规划通过记忆化(存储)这些子问题的解来避免重复计算。

3. 最优子结构

斐波那契数列问题具有最优子结构的特点,即问题的最优解包含了其子问题的最优解。虽然斐波那契数列问题本身不涉及“最优化”,但其解决方法体现了最优子结构的思想:F(n)的最优解(即准确解)可以通过组合F(n-1)F(n-2)的解得到。

4. 动态规划表(DP Table)

在这个例子中,dp数组就是所谓的动态规划表。它用来记录每一步的结果(即每个F(i)的值),以便于后续的计算可以直接引用前面的结果,而不是重新计算。这种方法极大地提高了效率,因为每个子问题只被解决一次,并且一旦被解决,其结果就会被保存。

5. 状态转移方程

动态规划的核心是状态转移方程。在斐波那契数列的例子中,状态转移方程是dp[i] = dp[i-1] + dp[i-2]。这个方程描述了问题状态之间的关系,即如何从已知的子问题的解得到当前问题的解。

通过这个斐波那契数列的例子,你可以看到,尽管我们使用的是简单的数组来存储每一步的结果,但这个过程完全体现了动态规划的思想:分解问题、解决子问题、存储子问题的解以避免重复计算。这就是dp数组与动态规划思想关联的方式,它是实现动态规划策略的一个工具。

总结

动态规划中的应用体现了动态规划的核心思想:存储中间结果,避免重复计算。这里的dp数组正是用来存储每个子问题的解,即斐波那契数列中每个位置的数值。让我们更深入地理解它与动态规划思想的关联:


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
1月前
|
存储 算法
深入了解动态规划算法
深入了解动态规划算法
57 1
|
1月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
4月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
72 8
|
12天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
29 2
|
1月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
67 2
动态规划算法学习三:0-1背包问题
|
1月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
65 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
1月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
122 0
动态规划算法学习二:最长公共子序列
|
1月前
|
存储 人工智能 算法
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
|
1月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
101 0
|
1月前
|
算法 Java 索引
数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
20 0