算法的基础掌握从递归开始,也就是《算法导论》中最初的分而治之思想。当然我们知道递归的效率是很低的,在很多情况下会出现指数级别的时间复杂度,因此学会将递归问题转化为迭代问题,是学习算法的下一个必经之路。其中有一大类问题就需要用到动态规划来解决。
对于一个优化问题来说,动态规划需要问题有以下两个特征:
- 最优子结构
- 子问题重叠行
我们从一到简单的斐波那契数列题目开始。
题目分析
来自兔群繁殖这一道题目。斐波那契数列又叫兔子数列,很容易想到,问题在于如何高效的求解这个问题。
递归版本
斐波那契求解方程如下:
因此很容易写出递归版本的解答
def fib_recursive(n: int) -> int:
if n == 1 or n == 2:
return 1
return fib_recursive(n - 1) + fib_recursive(n - 2)
然而这种方法存在明显的性能问题。每次计算时,都会重复计算相同的子问题。例如,计算 fib_recursive(5) 时会计算两次 fib_recursive(4) 和三次 fib_recursive(3),时间复杂度达到指数级别 。
动态规划的分析
动态规划的核心思想是自底向上解决问题,避免递归调用造成的额外栈空间开销。对于斐波那契数列,可以用一个数组 dp 来存储中间结果,其实就是提前存储好一张表,保存先前的计算信息,然后在后续解决问题时遇到先前的子问题就直接查表得到结果。
比如对于斐波那契数列,我们可以用一张简单的一维数组dp[i]来存储,并初始化递归基,也就是dp[1] = 1; dp[2] = 1
def fib_dp(n: int) -> int:
if n == 1 or n == 2:
return 1
dp = [0] * (n + 1)
dp[1] = dp[2] = 1
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
复杂度分析
我们用到了一维数组,空间复杂度,时间复杂度只需要填满这张表,因此是线性的。
进一步优化
实际上,我们只需要存储最近两个状态的值,而不需要整个数组。可以进一步优化为:
def fib_optimized(n: int) -> int:
if n == 1 or n == 2:
return 1
prev2, prev1 = 1, 1
for _ in range(3, n + 1):
curr = prev1 + prev2
prev2, prev1 = prev1, curr
return curr
这样,时间复杂度仍然是线性的,但是空间复杂度降低成了常数。
总结
这个简单的斐波那契数列动态规划,已经阐释了子问题重叠性这一核心思想,在下面的系列中,将通过优化问题来进一步阐释优化子结构这一思想
使用豆包marscode AI的优势
在解决这类问题时,豆包marscode AI可以提供以下优势:
- 代码生成:AI能够根据问题描述快速生成相应的代码框架,节省编写代码的时间。
- 算法优化:AI能够分析代码并提供可能的优化建议,帮助提高算法的效率。
- 错误检测:AI能够检测代码中的错误,并提供修改建议,确保代码的正确性。