用非人类的语言描述动态规划
下面我们从几个问题和思路来描述DP是什么东西,为了不跳楼,我们只举例序列和双序列类的DP
01
—
什么是DP
DP是一种问题的求解思路,从这点讲他和分治没有什么区别,都是通过组合子问题的解来求解原问题,有所不同的是分治是将问题拆解成不相交的子问题,计算量一般都比较反人类,而DP则是应用于子问题重叠的情况,保存子问题的最优解,避免不必要的计算。
那DP又有那些常见的形式呢?我们怎么可以确认哪些问题可以使用DP呢?一般呢,动态规划类题目有这样几个目的:最优解、方案可行性和总数等,如果碰到这些就大可一试了。而根据问题类型,又可分为序列和双序列、矩阵、划分、区间、背包、状态压缩、树以及花式DP。
我们如何去使用DP解决问题呢?我们会有几个常见的步骤,一步一步来,如果最后还是做不出来,就说明可以抄题目了。第一步:刻画最优解的结构;第二步:递归的计算最优解;剩下的我忘了,但这是核心两步。。,我们去试一下怎么使用这两步。
02
—
第一级:钢条切割问题
问题描述:Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。
切割后的两段钢条可以看成是两个独立的问题,而原问题又是这两个问题最优解的组合,这符合最优子结构的特征:问题的最优解由相关子问题的最优解组合而成,这怎么理解呢,假如我们有一根十米的钢条,我们能切成1和9,2和8,3和7。。。,再往下9能分成3和6,暂停,3出现两次了,我们会将3的最优解第一次计算后就保存起来,以后直接用,这样大问题的最优解就用到了小问题的最优解,避免了不必要的计算,这样实现出来就是带备忘录的自顶向下DP了。
自底向上会更加简洁一些,伪代码如下:
func(p, n)
// r保存了算过的最优解,0就是0,下面开始从1计算,直到n
let r[0..n] be a new array
r[0]=0
for j = 1 to n // j就是将要计算最优解的长度
q = -1000
for i = 1 to j // 从1到j将j锯开,挨个算max最优解
q = max(q, p[i]+r[j-i])
r[j] = q
return r[n]
02
—
第二级:跳跃游戏
题目描述:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
这个问题的最优子结构是什么,就是原问题可以通过组合什么子问题来解,第一个位置能否到最后一个取决于第一个位置可达的格子能不能到最后,中间路上的格子的最优解决定了第一个格子的解,我们将每个格子是否可达保存下来,和上面的类似,原问题到子问题的转换可以看作一个状态转换方程,从最优子结构到状态转移方程就是从理论到编码的实现过程,伪代码如下:
func(p)
// r每个格子是否可达,路上格子可达的就存起来
let r[0..n] be a new array
r[0]=1
for j = 1 to n
for i = 0 to j
if r[i] == 1 and p[i] + i >= j
r[j] = 1
return r[n]
02
—
最后的高级一点的的:最长公共子序列
问题描述:有俩序列,找他们最长公共子序列,最长公共子序列不是连续的最长公共子序列,比如a = [q, a, w , b, e, c, r, d] , b = [q,m, e, n, w, g, r, t], [q, w, e, r, ] 是他们的最长公共子序列。
这个问题不太好想的一点是:对于序列X(x1,x2,x3,x4...xm)和序列Y(y1,y2,y3,y3,...yn)的最长公共子序列Z(z1,z2,z3,,,,zk)有以下特征:
1、如果xm=yn:Zk-1 是 Xm-1和Yn-1的一个最长公共子序列
2、否则,若Zk != Xm ,Zk就是Xm-1和Yn的一个最长公共子序列
3、否则,若Zk != Yn ,Zk就是Xm和Yn-1的一个最长公共子序列
从这个规则找到状态转移方程
1、当i=0或j=0时,C[i,j] = 0;
2、当Xi=Yj时,C[i,j] = C[i-1,j-1] + 1
3、否则,看特征2,3,不想打公式了
先求解X1和Y1的最长公共子序列,得到C[1,1] = 0,然后求X1和Y1,Y2的LCS,依次类推,记录每个X和Y的LCS,直到遍历完整个空间,得到最终的LCS。
假设,X{A,D,V,F,F,D,F,G,G},Y{W,F,R,R,H,H,D,T,I}简单总结就是,最终的LCS也就是C[9,9],按照特征需要C[8,8]和C[8,9],C[9,8],而后续的三者又分别需要C[7,7]......,再后续又需要C[6,6]........,直到需要C[1,1]C[0,0],C[0,1],C[1,0],他们一定为0或单个字符的比较。就这样不断的分治获得结果。
后面背包、树、状态压缩,在详细说一下这个,以及状态转移方程和每种类型DP的状态是什么,比如序列的状态就是下标