动态规划是解决一类问题的方法,而不是某种固定的算法。
eg: 求最长公共子序列(LCS: Longest Common Subsequence)
现存在两个序列:
序列x
: A B C B D A B
序列y
: B D C A B A
则LCS(x
y
) = { (B D A B), (B C A B), (B C B A) },存在3个最长公共子序列
①: 穷举法:
1.判断一个子序列存不存在于某个序列中的时间复杂度是O(n);
2.一个m长度的序列最多有2^m-1个子序列。Why?
假设我们把m长度序列看成一个长度为m的位向量,每个位上只能是0或1(标为1则表示这个元素被放入子序列),则可能存在2^m 种情况,在排除全为0的情况,则存在最坏子序列为2^m-1个(可能有重复)
穷举法的时间复杂度:O(n*2^m)
②: 简化:
1.先计算出len[ LCS(x,y)],最长公共子序列的长度
2.再找出符合该长度的公共子序列
定义 c[i,j] = len[ LCS(x₁...ᵢ, y₁...) ] (length of LCS(x₁...ᵢ, y₁...)), 则
Proof: Case x[i] = y[j]
x : 1 2 .. i ... m
y: 1 2 ......j....n
当x[i]=y[j]时,let z[1...k] = LCS(x[1...i],y[1...j]), where c[i,j] = k
Then,z[k] = x[i] = y[j],or else z could be extended by tacking on x[i].
Thus,z[1...k-1] = LCS(x[1...i-1] , y[1...j-1])
OtherWise Case x[i] != y[j] 同理可证
动态规划的特征(当你遇到这种结构的问题时,就可以使用户动态规划):
1.一个问题的最优解包含了子问题的最优解;
2.重叠子问题(一个递归的过程包含少数独立的子问题被重复计算了多次)
Code:
// 计算LCS(x,y,i,j)
if x[i] = y[j]
then c[i,j] <- LCS(x,y,i-1,j-1)+1
else c[i,j] <- max{ LCS(x,j,i,j-1) LCS(x,y,i,j-1) }
return c[i,j]
最坏情况:x[i] != x[j]
Recursion tree(m=7,n =6)的情况:
![](https://img.haomeiwen.com/i12457267/ae91bfb25213f4d1.png)
树的高度:height = m+n
时间复杂度:O(2^(m+n))
动态规划的第二个特征:重叠子问题
![](https://img.haomeiwen.com/i12457267/b91d9881432ab7cd.png)
从图1.1可以看出,某些情况被重复计算了多次,剔除重复的情况,有
m乘以n(m,n依次递减1)个独立的子问题。
③: 备忘法:
将计算子问题的结果保存,如果当你需要这个问题时,就不需重复再计算。
// 计算LCS(x,y,i,j)
if c[i,j] = nil
then if x[i] = y[j]
then c[i,j] <- LCS(x,y,i-1,j-1)+1
else c[i,j] <- max{ LCS(x,j,i,j-1) LCS(x,y,i,j-1) }
return c[i,j]
时间复杂度: O(mn)
空间复杂度:O(mn)
④: 自底向上的备忘法:
可以有效减少空间复杂度。
空间复杂度:O(min(m,n))
网友评论