动态规划--最长公共子序列

  动态规划是解决一类问题的方法,而不是某种固定的算法。

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)的情况:


动态规划--最长公共子序列_第1张图片
1.1.png

树的高度:height = m+n
时间复杂度:O(2^(m+n))

动态规划的第二个特征:重叠子问题

1.2.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))

你可能感兴趣的:(动态规划--最长公共子序列)