动态规划一直是算法设计里面比较难的东西,在考试里也算是压轴题
考试的时候常常会考这四样东西:
- 第一问写优化子结构和子问题重叠性,并要求证明之。
- 第二问写dp方程(就是关键部分递归的方程)
- 第三问写伪代码
- 第四部分时间复杂度
动态规划的要点就是,先定义出代价的表达形式,然后再假定自己的设计的算法已经能够解决对应的子问题,然后再根据子问题的解组合出当前这一层问题的最优解(分治算法也是这个思想)。
需要注意的是还需要定义最简单问题的解,也就是递归方程的终止条件,最后输出优化解也是一个大问题。很多问题往往是变成一个填表题,我们需要确定填表顺序即可。
动态规划算法的设计步骤
- 分析优化解的结构
- 递归地定义最优解的代价
- 自底向上地计算优化解的代价保存之,并获取构造最优解的信息
- 根据构造最优解的信息构造优化解
优化子结构和子问题重叠性
首先看一下优化子结构的定义:
当一个问题的优化解包含了子问题的优化解时,我们说这个问题具有优化子结构。优化子结构使得我们能自下而上地完成求解过程,缩小子问题集合,只需那些优化问题中包含的子问题,降低实现复杂性 。
一般地,我们先假设 问题S可以分为子问题A和B,如果问题具有优化子结构,S可以由子问题A和B各自独立的优化解组合而成,如果S是最优解,那么A和B一定是最优解,这个可以根据题目的具体情景分析出来。
案例:
给定一个整数序列 a1,…,an。相邻两个整数可以合并, 合并两个整数的代价 是这两个整数之和。通过不断合并最终可以将整个序列合并成一个整数, 整 个过程的总代价是每次合并操作代价之和。试设计一个动态规划算法给出 a1,…,an 的一个合并方案使得该方案的总代价最大
证明:
若合并 a1,…,an 的最优解最后一次合并操作在 a1,…,ak−1 和 ak,…,an 之间,即最后一次操作合并 a1 + … + ak−1 和 ak + … + an,2 ≤ k ≤ n,则 在合并 a1,…,an 的优化顺序中,对应子问题合并 a1,…,ak−1 的解必须是合并 a1,…,ak−1 的优化解,对应子问题合并 ak,…,an 的解必须是合并 ak,…,an 的 优化解。因此问题具有优化子结构,问题的优化解包含子问题的优化解。
重叠子问题:
就是在问题的求解过程中,很多子问题的解将被多次使用。
一般都是写一个不太大也不太小例子,再对例子的子问题拆出来进行求解,看它的子问题的子问题是什么,看看这些子问题有没有重叠子问题,不要太简单,也不要太复杂,一般是4,5,6个的规模就可以了。
例如:对于矩阵链乘问题
写dp方程
这是动态规划里面最难的部分,如果dp方程会写,题目差不多已经解决一大大大大半了。
这个没有一般的方法,但是例题可以启发我们
输入数组 A[0:n] 和正实数 d, 试设计一个动态规划算法输出 A[0:n] 的一个最长子序列, 使得子序列中相继元素之差的绝对值不超过 d。分析算法的时间 复杂度:
给定数列 A[1 : n],以 A[i] 为满足条件的最长子序列的最后一个元素时,所得最长子序列的长度记作 L[i],可得递推式:
L [ i ] = m a x ( 1 , m a x 1 ≤ k ≤ i , A [ k ] − A [ i ] ∣ ≤ d ( L [ k ] + 1 ) ) , 1 ≤ i ≤ n L[i]=max(1,max_{1 \leq k \leq i ,A[k]−A[i]|≤d}(L[k]+1)) ,1 \leq i \leq n L[i]=max(1,max1≤k≤i,A[k]−A[i]∣≤d(L[k]+1)),1≤i≤n
当不存在 k 满足 1 ≤ k < i && |A[k]−A[i]| ≤ d 时,就将 L[i] 置为 1。
给定一个整数序列 a1,…,an。相邻两个整数可以合并, 合并两个整数的代价 是这两个整数之和。通过不断合并最终可以将整个序列合并成一个整数, 整 个过程的总代价是每次合并操作代价之和。试设计一个动态规划算法给出 a1,…,an 的一个合并方案使得该方案的总代价最大
设
S
[
i
,
j
]
=
a
i
+
a
i
+
1
+
.
.
.
.
+
a
j
S[i,j]=a_i+a_{i+1}+....+a_j
S[i,j]=ai+ai+1+....+aj
m
[
i
,
j
]
m[i,j]
m[i,j]表示合并
a
i
+
a
i
+
1
+
.
.
.
.
+
a
j
a_i+a_{i+1}+....+a_j
ai+ai+1+....+aj的最大代价
代价方程
m
[
i
,
j
]
=
0
,
i
=
j
m[i,j]=0,i=j
m[i,j]=0,i=j这很显然
m
[
i
,
j
]
=
m
a
x
i
+
1
≤
k
≤
j
(
m
[
i
,
k
−
1
]
+
m
[
k
,
j
]
)
+
S
[
i
,
j
]
,
i
<
j
m[i,j]=max_{i+1\leq k \leq j}(m[i,k-1]+m[k,j])+S[i,j],i< j
m[i,j]=maxi+1≤k≤j(m[i,k−1]+m[k,j])+S[i,j],i<j
这个的思想也不难想,首先第一条显然是对的
第二条可以类比矩阵链乘问题,讲一个序列的乘积看作是两个子序列的最大乘积之和加上合并的代价
伪代码和时间复杂度
如果有了dp方程,伪代码不难写,注意语法即可,
不过动态规划问题很多代码都分为两个部分,分别是计算代码和输出结果代码
动态规划的复杂度一般分析伪代码就可以得出,dp大多涉及循环,因此看循环的层数差不多可以确定了,一般都是: O ( n 3 ) O(n^3) O(n3) , O ( n 2 ) O(n^2) O(n2) , O ( n ) O(n) O(n)左右