前言:
谈到动态规划,其实最好的切入方向,就是先了解一下最经典的几个例子;找出这些例子的共性;再凝聚这些特征进行模型的分类;分类完成后,在每个分类里面学几个经典的例题。例题选好了,就可以去刷 leetcode。这样下来;动态规划的水平也就形成了。
阶段一:入门了解的例子
例1:求第 n 个斐波那契数列
方法一:递归
既然递归式子都列出来了,那么最直男的方法必定就是它了,不多讲。
方法二:将递归改成循环
可以注意到,递归的方法是一种从后 f(n)往前形成生成树的方法,这里面会产生很多不必要的重复计算;当我们将递归改为从1开始到n的循环的时候,也就是从前往后的方法,就可以避免一些不必要的重复计算,也就可以降低时间复杂度了。
方法三:将递归改成动态规划
斐波那契数列问题是递归的;考虑它有一个特别突出的结构特性。就是它具有:
- 重叠子问题。(这也是动态规划的要素之一)
- 最优子结构。(这一点貌似。。但是尽管这不是优化问题,但套用还是没有问题的)
long long memo[N+1]; // 全局变量,初始化为0
long long fib(unsigned n)
{
if (n <= 1)
return n;
if (memo[n]) return memo[n];
else
return memo[n] = fib(n-1) + fib(n-2);
}
----------------
例2. 求最短的路径问题
方法一:枚举算法:如果网络层数为 n 。复杂度接近2的n次方。
方法二:动态规划算法:
这里关键是利用了两个动态规划的要素:
- 更大问题的最优结果可以利用其子问题的最优结果。(最优子结构)
- 重叠子问题。
例3 矩阵相乘 最优顺序
题目:
给定n个矩阵{A1,A2,A3...An};其中Ai,Ai+1是可乘的。但是你可以加括号来改变它们相乘的顺序。请确定一个顺序来使这个连乘的计算量最小?
我将就本题,给出标准的动态规划设计步骤:
前提:读懂题意,知道两个矩阵相乘的计算量主要体现在(前一个矩阵的行*列 * 后一个矩阵的 列)
1)找出最优解的大概的整体形式(主要是看看子问题和整体最优解)之间的结构特征,期间要做的就是把整个问题求解变量化,设计出边界和分隔的点。
回到本题就是:
数据模型化:将{A1,A2,A3...An}简写成A[1-n]的形式;假设在k处分割成两个子问题,当我们一般化 A[i,j]时,k的取值范围也确定在 i <= k <= j-1 之间。
找出最优解与次子结构的关系式:最优解 = 子问题1的最优解 + 子问题2的最优解 + 此时子问题1的大矩阵和子问题2的大矩阵相乘的计算量
这里的最优解,就在于我们确定k的位置。在循环里面逐个尝试。。。
2)列出具体的递归关系(整体的递归形式解)
方便起见:设A[i,j]的最优解是 m[i,j];
由于原问题的矩阵连乘形式是A[1,n]的形式;故这里可以确定 1<= i <= j <n;注意到当子问题的 i==j 时,m[i,j]=m[i,i]= 0(单个矩阵没有计算量)
当i<j时,说明子问题还没有解决,还可以分解。
m[i,j] =
到这里这个问题的递归解可以得出,代码如下:
# 本题的递归求解程序
def recsolution(i, j):
ans = []
if i == j:
return 0
else:
for k in range(i, j):
opt = recsolution(i, k) + recsolution(k+1, j) + s[i-1]*s[k]*s[j]
if not ans:
ans = opt
else:
if opt < ans:
ans = opt
return ans
ans = []
s = [30, 35, 15, 5, 10, 20, 25]
print(recsolution(1, 6))
3)用动态规划优化递归
因为重复计算,递归的复杂度可想而知:
由于这个题它满足:
- 最优子结构
- 重叠子问题
所以在此,我们用动态规划去优化: