动态规划( D ynamic P rogramming ,因此咱们简称动态规划为 DP )是 运筹学 的一个分支,是求解决策过程(decision process) 最优化的数学方法。 20 世纪 50 年代初 美国 数学家R.E.Bellman 等人在研究多阶段决策过程 (multistep decision process) 的优化问题时,提出了著名的最优化原理 (principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法 —— 动态规划。 1957 年出版了他的名著《 Dynamic Programming 》,这是该领域的第一本著做。ios
动态规划算法一般基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只须要多项式时间复杂度,所以它比回溯法、暴力法等要快许多。算法
说了这么多术语,想必你们都很头疼, 如今让咱们经过一个例子来了解一下DP 的基本原理。数组
首先,咱们要找到某个状态的最优解,而后在它的帮助下,找到下一个状态的最优解。 这句话暂时理解不了不要紧,请看下面的例子 :post
若是咱们有面值为1 元、 3 元和 5 元的硬币若干枚,如何用最少的硬币凑够 11 元?优化
咱们凭直观感受告诉本身,先选面值最大,所以最多选 2枚 5 元的硬币,如今是 10 元了,还差一元,接下来咱们挑选第二大的 3 元硬币,发现不行( 10+3=13 超了),所以咱们继续选第三大的硬币也就是 1元硬币,选一个就能够( 10+1=11 ),因此总共用了 3 枚硬币凑够了 11 元。这就是贪心法,每次选最大的。可是咱们将面值改成 2 元, 3 元和 5 元的硬币,再用贪心法就不行了。为何呢?按照贪心思路,咱们一样先取 2 枚最大 5 元硬币,如今 10 元了,还差一元,接下来选第二大的,发现不行,再选第三大的,仍是不行,这时用贪心方法永远凑不出 11 元,可是你仔细看看,其实咱们能够凑出 11 元的, 2 枚 3元硬币和 1 枚五元硬币就好了,这是人通过思考判断出来了的,可是怎么让计算机算出来呢?这就要用动态规划的思想:spa
首先咱们思考一个问题,如何用最少的硬币凑够i 元 (i<11) ?为何要这么问呢?两个缘由: 1. 当咱们遇到一个大问题时,老是习惯把问题的规模变小,这样便于分析讨论。 2. 这个规模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是同样的,本质上它仍是同一个问题 ( 规模变小后的问题实际上是原问题的子问题 ) 。.net
好了,让咱们从最小的i 开始吧。当 i=0 ,即咱们须要多少个硬币来凑够 0 元。因为 1 , 3 , 5 都大于 0,即没有比 0 小的币值,所以凑够 0 元咱们最少须要 0 个硬币。 ( 这个分析很傻是否是?别着急,这个思路有利于咱们理清动态规划究竟在作些什么。 ) 这时候咱们发现用一个标记来表示这句 “ 凑够 0 元咱们最少须要 0 个硬币。 ” 会比较方便,若是一直用纯文字来表述,不出一下子你就会以为很绕了。那么,咱们用 d(i)=j 来表示凑够 i 元最少须要 j 个硬币。因而咱们已经获得了 d(0)=0 ,表示凑够 0 元最小须要 0 个硬币。当 i=1 时,只有面值为 1 元的硬币可用,所以咱们拿起一个面值为 1 的硬币,接下来只须要凑够 0 元便可,而这个是已经知道答案的,即 d(0)=0 。因此, d(1)=d(1-1)+1=d(0)+1=0+1=1 。当 i=2 时,仍然只有面值为 1 的硬币可用,因而我拿起一个面值为 1 的硬币,接下来我只须要再凑够 2-1=1 元便可 ( 记得要用最小的硬币数量 ) ,而这个答案也已经知道了。因此 d(2)=d(2-1)+1=d(1)+1=1+1=2 。一直到这里,你均可能会以为,好无聊,感受像作小学生的题目似的。由于咱们一直都只能操做面值为 1 的硬币!耐心点,让咱们看看 i=3 时的状况。当 i=3 时,咱们能用的硬币就有两种了: 1 元的和 3 元的 ( 5 元的仍然没用,由于你须要凑的数目是 3 元! 5 元太多了亲 ) 。既然能用的硬币有两种,我就有两种方案。若是我拿了一个 1 元的硬币,个人目标就变为了:凑够 3-1=2 元须要的最少硬币数量。即 d(3)=d(3-1)+1=d(2)+1=2+1=3 。这个方案说的是,我拿 3 个 1 元的硬币;第二种方案是我拿起一个 3 元的硬币,个人目标就变成:凑够 3-3=0 元须要的最少硬币数量。即 d(3)=d(3-3)+1=d(0)+1=0+1=1. 这个方案说的是,我拿 1 个 3 元的硬币。好了,这两种方案哪一种更优呢?记得咱们但是要用最少的硬币数量来凑够 3元的。因此,选择 d(3)=1 ,怎么来的呢?具体是这样获得的: d(3)=min{d(3-1)+1, d(3-3)+1} 。设计
OK,码了这么多字讲具体的东西,让咱们来点抽象的。从以上的文字中,咱们要抽出动态规划里很是重要的两个概念:状态和状态转移方程。code
上文中d(i) 表示凑够 i 元须要的最少硬币数量,咱们将它定义为该问题的 " 状态 " ,这个状态是怎么找出来的呢?根据子问题定义状态。你找到子问题,状态也就浮出水面了。最终咱们要求解的问题,能够用这个状态来表示: d(11) ,即凑够 11 元最少须要多少个硬币。那状态转移方程是什么呢?既然咱们用 d(i)表示状态,那么状态转移方程天然包含 d(i) ,上文中包含状态 d(i) 的方程是: d(3)=min{d(3-1)+1, d(3-3)+1} 。没错,它就是状态转移方程,描述状态之间是如何转移的。固然,咱们要对它抽象一下,htm
d(i)=min{ d(i-v j )+1 },其中 i-v j >=0, v j 表示第j 个硬币的面值 ;
有了状态和状态转移方程,这个问题基本上也就解决了。固然了,Talk is cheap,show me the code!
下图是当i 从 0 到 11 时的解:
从上图能够得出,要凑够11 元至少须要 3 枚硬币。
此外,经过追踪咱们是如何从前一个状态值获得当前状态值的,能够找到每一次咱们用的是什么面值的硬币。好比,从上面的图咱们能够看出,最终结果d(11)=d(10)+1( 面值为 1) ,而 d(10)=d(5)+1( 面值为 5),最后 d(5)=d(0)+1 ( 面值为 5) 。因此咱们凑够 11 元最少须要的 3 枚硬币是: 1 元、 5 元、 5 元。
经过硬币问题咱们初识 DP的原理,其实能够说贪心问题是 DP 问题的特例,如今咱们经过几道题目加深对 DP 问题的理解
数塔问题 是动态规划经典的题目,下面来初步讲解下
将一个由N 行数字组成的三角形,如图因此,设计一个算法,计算出三角形的由顶至底的一条路径,使该路径通过的数字总和最大。
学弟学妹们大家以前学过 DFS和 BFS ,第一眼看过去这题应该用 DFS 解决,没错, DFS 也能够,可是咱们观察下 n 行总共有 (1 + 2 + 3 + 4+...+n) = ( 1+n ) *n/2 个节点,在递归求解的过程当中不少节点被重复访问了,这就致使时间大大增长,必然超时
好比用递归的话,18 这个节点被访问了两次
可是若是用DP的话这个节点能够只访问一次
好了,如今咱们用DP解决这道问题
将上图转化一下:
假设上图用map[][] 数组保存。
令f[i][j] 表示从顶点 (1, 1) 到顶点 (i, j) 的最大值。
则能够获得状态转移方程:
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + map[i][j]
此题既适合自顶而下的方法作,也适合自底而上的方法,
当用自顶而下的方法作时,最后要在在最后一列数中找出最大值,
而用自底而上的方法作时,f[1][1] 即为最大值。
因此咱们将图2 根据状态转移方程能够获得图 3 :
最大值是30.
代码以下:
上面讨论了 两个 很是简单的例子。如今让咱们来看看对于更复杂的问题,如何找到状态之间的转移方式(即找到状态转移方程 ) 。为此咱们要引入一个新词叫递推关系来将状态联系起来 ( 说的仍是状态转移方程)
OK,上例子,看看它是如何工做的。
一个序列有N 个数: A[1],A[2],…,A[N] ,求出最长非降子序列的长度。 ( 讲 DP 基本都会讲到的一个问题LIS : longest increasing subsequence)
正如上面咱们讲的,面对这样一个问题,咱们首先要定义一个“ 状态 ” 来表明它的子问题,而且找到它的解。注意,大部分状况下,某个状态只与它前面出现的状态有关,而独立于后面的状态。
让咱们沿用“ 入门 ” 一节里那道简单题的思路来一步步找到 “ 状态 ” 和 “ 状态转移方程 ” 。假如咱们考虑求A[1],A[2],…,A[i] 的最长非降子序列的长度,其中 i<N ,那么上面的问题变成了原问题的一个子问题 ( 问题规模变小了,你可让 i=1,2,3 等来分析 ) 而后咱们定义 d(i) ,表示前 i 个数中以 A[i] 结尾的最长非降子序列的长度。 OK ,对照 “ 入门 ” 中的简单题,你应该能够估计到这个 d(i) 就是咱们要找的状态。若是咱们把 d(1) 到 d(N) 都计算出来,那么最终咱们要找的答案就是这里面最大的那个。状态找到了,下一步找出状态转移方程。
为了方便理解咱们是如何找到状态转移方程的,我先把下面的例子提到前面来说。若是咱们要求的这N 个数的序列是:
5, 3 , 4 , 8 , 6 , 7
根据上面找到的状态,咱们能够获得:(下文的最长非降子序列都用LIS 表示)
· 前1 个数的 LIS 长度 d(1)=1( 序列: 5)
· 前2 个数的 LIS 长度 d(2)=1( 序列: 3 ; 3 前面没有比 3 小的 )
· 前3 个数的 LIS 长度 d(3)=2( 序列: 3 , 4 ; 4 前面有个比它小的 3 ,因此 d(3)=d(2)+1)
· 前4 个数的 LIS 长度 d(4)=3( 序列: 3 , 4 , 8 ; 8 前面比它小的有 3 个数,因此d(4)=max{d(1),d(2),d(3)}+1=3)
OK,分析到这,我以为状态转移方程已经很明显了,若是咱们已经求出了 d(1) 到 d(i-1) ,那么 d(i) 能够用下面的状态转移方程获得:
d(i) = max{1, d(j)+1},其中 j<i,A[j]<=A[i]
用大白话解释就是,想要求d(i) ,就把 i 前面的各个子序列中,最后一个数不大于 A[i] 的序列长度加 1 ,而后取出最大的长度即为 d(i) 。固然了,有可能 i 前面的各个子序列中最后一个数都大于 A[i] ,那么d(i)=1 ,即它自身成为一个长度为 1 的子序列。
分析完了,上图:( 第二列表示前 i 个数中 LIS 的长度,第三列表示, LIS 中到达当前这个数的上一个数的下标,根据这个能够求出 LIS 序列 )
代码:
该算法的时间复杂度是O(n 2 ),并非最优的解法。还有一种很巧妙的算法能够将时间复杂度降到O(nlogn) ,网上已经有各类文章介绍它,这里就再也不赘述。此题还能够用 “ 排序 +LCS” 来解,感兴趣的话可自行 Google , Baidu 。
最后讲一下最长上升公共子序列问题 :
什么是最长公共子序列呢 ? 比如一个数列S,若是分别是两个或多个已知数列的子序列,且是全部符合此条件序列中最长的,则S 称为已知序列的最长公共子序列。
举个例子,如:有两条随机序列,如 1 3 4 5 5 , and 2 4 5 5 7 6 ,则它们的最长公共子序列即是: 4 5 5。
·
·
解最长公共子序列问题时最容易想到的算法是穷举搜索法,即对 X 的每个子序列,检查它是否也是 Y的子序列,从而肯定它是否为 X 和 Y 的公共子序列,而且在检查过程当中选出最长的公共子序列。 X 和 Y的全部子序列都检查事后便可求出 X 和 Y 的最长公共子序列。 X 的一个子序列相应于下标序列{1, 2, …, m} 的一个子序列,所以, X 共有 2m个不一样子序列(Y 亦如此,如为 2^n ),从而穷举搜索法须要指数时间( 2^m * 2^n )。
· 动态规划算法
事实上,最长公共子序列问题也有最优子结构性质。
记:
Xi=﹤ x1 , ⋯ , xi ﹥即 X 序列的前 i 个字符 (1≤i≤m) (前缀)
Yj=﹤ y1 , ⋯ , yj ﹥即 Y 序列的前 j 个字符 (1≤j≤n) (前缀)
假定Z= ﹤ z1 , ⋯ , zk ﹥ ∈LCS(X , Y) 。
·
若 xm=yn (最后一个字符相同),则不难用反证法证实:该字符必是X 与 Y 的任一最长公共子序列 Z(设长度为 k )的最后一个字符,即有 zk = xm = yn 且显然有 Zk-1∈LCS(Xm-1 , Yn-1) 即 Z 的前缀 Zk-1是 Xm-1 与 Yn-1 的最长公共子序列 。此时,问题化归成求Xm-1 与 Yn-1 的 LCS ( LCS(X , Y)的长度等于 LCS(Xm-1 , Yn-1 )的长度加 1 )。
·
·
若 xm≠yn ,则亦不难用反证法证实:要么Z∈LCS(Xm-1, Y) ,要么 Z∈LCS(X , Yn-1) 。因为 zk≠xm 与zk≠yn 其中至少有一个必成立,若 zk≠xm 则有 Z∈LCS(Xm-1 , Y) ,相似的,若 zk≠yn 则有Z∈LCS(X , Yn-1) 。此时,问题化归成求 Xm-1 与 Y 的 LCS 及 X 与 Yn-1 的 LCS 。 LCS(X , Y) 的长度为: max{LCS(Xm-1 , Y) 的长度 , LCS(X , Yn-1) 的长度 } 。
·
因为上述当xm≠yn的状况中,求LCS(Xm-1 , Y) 的长度与 LCS(X , Yn-1) 的长度,这两个问题不是相互独立的:二者都须要求 LCS(Xm-1 , Yn-1) 的长度。另外两个序列的 LCS 中包含了两个序列的前缀的 LCS,故问题具备最优子结构性质考虑用动态规划法。
也就是说,解决这个 LCS 问题,你要求三个方面的东西:一、LCS ( Xm-1 , Yn-1 ) +1 ;二、LCS (Xm-1 , Y ), LCS ( X , Yn-1 );三、max{LCS ( Xm-1 , Y ), LCS ( X , Yn-1 ) }。
行文至此,其实对这个 LCS 的动态规划解法已叙述殆尽,不过,为了成书的某种必要性,下面,我试着再多加详细阐述这个问题。
3.一、最长公共子序列的结构
最长公共子序列的结构有以下表示:
设序列 X=<x1, x2, …, xm>和 Y=<y1, y2, …, yn>的一个最长公共子序列 Z=<z1, z2, …, zk>,则:
1. 若x m =y n ,则z k =x m =y n 且Z k-1 是X m-1 和Y n-1 的最长公共子序列;
2. 若x m ≠y n 且z k ≠x m , 则Z 是 X m-1 和Y 的最长公共子序列;
3. 若x m ≠y n 且z k ≠y n ,则 Z 是 X 和 Y n-1 的最长公共子序列。
其中 Xm-1=<x1, x2, …, xm-1>, Yn-1=<y1, y2, …, yn-1>, Zk-1=<z1, z2, …, zk-1>。
由最长公共子序列问题的最优子结构性质可知,要找出 X=<x1, x2, …, xm>和 Y=<y1, y2, …, yn>的最长公共子序列,可按如下方式递归地进行:当 xm=yn时,找出Xm-1和Yn-1的最长公共子序列,而后在其尾部加上xm(=yn)便可得 X 和 Y 的一个最长公共子序列。当 xm≠yn时,必须解两个子问题,即找出Xm-1和Y 的一个最长公共子序列及 X 和 Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X 和 Y 的一个最长公共子序列。
由此递归结构容易看到最长公共子序列问题具备子问题重叠性质。例如,在计算 X 和 Y 的最长公共子序列时,可能要计算出 X 和 Yn-1及Xm-1和Y 的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算 Xm-1和Yn-1的最长公共子序列。
与矩阵连乘积最优计算次序问题相似,咱们来创建子问题的最优值的递归关系。用 c[i,j] 记录序列 Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>, Yj=<y1, y2, …, yj>。当 i=0 或 j=0 时,空序列是 Xi和Yj的最长公共子序列,故c[i,j]=0 。其余状况下,由定理可创建递归关系以下:
代码以下 :
讲到这想必对 DP问题有一个大概的认识了吧?乘热打铁,咱们去 HDU 刷几道简单题练练手感!
HDU2191
HDU1159
HDU1432
HDU2084