概述:动态规划在查找有很多重叠子问题的情况的最优解时有效。他将问题重新组合成子问题。为了避免多次解决这些子问题,他们的结果都逐渐被计算并被保存。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于最优子结构问题,最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
适用情况:1.最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。最优子结构性质为动态规划算法解决问题提供了重要线索。
2. 无后效性。即子问题的解一旦确定,就不再改变,不受在这之后,包含他的最大的问题的求解决策影响。
3. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不是总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地察看一下结果,从而获得较高的效率。
动态规划的本质不在于是递推或是递归,也不需要纠结是不是内存换时间。
理解动态规划并不需要数学公式介入,首先需要明白哪些问题不是动态规划可以解决的,才能明白为什么需要动态规划,不过好处是顺便也就搞明白了递推贪心搜索和动归之间有什么关系。
动态规划是对于某一类问题的解决方法,重点在于如何鉴定某一类问题是动态规划可解的而不是纠结解决方法是递归还是递推。
怎么鉴定dp可解的一类问题需要从计算机是怎么工作的说起,计算机的本质是一个状态机,内存里存储的所有数据构成了当前的状态,cpu只能利用当前的状态计算出下一个状态。当你企图使用计算机解决一个问题时,其实就是在思考如何将这个问题表达成状态(用哪些变量存储哪些数据)以及如何在状态中转移(怎样根据一些变量计算出另一些变量)
动态规划的本质,是对问题状态的定义和状态转移方程的定义。
举个例子:比如我想计算第100个斐波那契数,每一个斐波那契数就是这个问题的一个状态每求一个新数字只需要之前的两个状态,所以同一时刻,最多只需要保存两个状态,空间复杂度就是常数;每计算一个新状态所需要的时间也是常数且状态是线性递增的,所以时间复杂度也是线性的。
上面这种状态计算很直接,只需要固定的模式从旧状态计算出新状态就行(a[i]=a[i-1]+a[i-2]),不需要考虑是不是需要更多的状态,也不需要选择哪些旧状态来计算新状态。对于这样的解法,我们叫递推。
斐波那契数过于简单,以至于让人们忽视了阶段的概念,所谓阶段是指随着问题的解决,在同一个时刻可能会得到的不同的状态的集合。斐波那契数中,每一步会计算得到一个新数字,所以每个阶段只有一个状态。想象另一个问题情境,加入把你放在一个围棋上的某一点,你每一步之能走一格,因为你可以东南西北随便走,所以你当你同样走四步可能会处于很多不同的位置。从头开始走了几步就是第几个阶段,走了n步可能处于的位置称为一个状态,走了这n步所有可能到达的位置的集合就是这个阶段下所有可能的状态。
现在问题来了,有了阶段之后,计算新状态可能会遇到各种奇葩的情况,针对不同情况,就需要不同的算法,下面分情况来说明一下:
假如问题有n个阶段,每个阶段有多个状态,不同阶段的状态数不必相同,一个阶段的一个状态可以得到下个阶段的所有状态中的几个。那我们要计算出最终阶段的状态数自然要经历之前每个阶段的某些状态。
好消息是,有时候我们不需要真的计算所有状态,比如一个棋盘问题:从棋盘的左上角到达右下角最短需要几步。答案很显然,用这样的一个问题是为了帮助我们理解阶段和状态。某个阶段确实可以有多个状态,正如这个问题中走几步可以走到很多位置一样。但是同样n步中,有哪些位置可以让我们在第n+1步中走的最远呢?没错,正是第n步中走的最远的位置。换成一句熟悉话叫做“下一步最优是从当前最优得到的”。所以为了计算最终的最优值,只需要存储每一步的最优值即可,解决这种性质的问题的算法叫做贪心。如果只是看最优状态之间的计算过程是不是和斐波那契数很像?所以计算的方法是递推。
既然问题都是可以划分成阶段和状态的。这样一来我们一下子解决了一大堆的问题:一个阶段的最优可以由前一个阶段的最优得到。
如果一个阶段的最优无法用前一个阶段的最优得到呢?
再来一个迷宫的例子。在计算从起点到终点的最短路线时,不能只保存当前阶段的状态,因为题目要求你最短,所以必须知道之前走过的所有位置。因为即便你当前在的位置不变,之前的路线不同会影响你的之后走的路线。这时你需要保存的是之前每个阶段所经历的那个状态。根据这些信息才能计算出下一个状态!
每个阶段的状态或许不多,但是每个状态都可以转移到下一个阶段的多个状态,所以解的复杂度就是指数的,因此时间复杂度也是指数的。刚刚提到的之前的路线会影响到下一步的选择,叫做后效性。
刚刚的情况实在太普遍,解决方法太暴力,有没有哪些情况可以避免如此的暴力?
契机在于后效性,有一类问题,看似需要之前所有的状态,其实不用。不妨也拿最长上升子序列的例子来说明为什么他不必需要暴力搜索,进而引出动态规划思路。
假如我们年幼无知想用搜索去寻找最长上升子序列。怎么搜索呢?需要从头到尾一次枚举是否选择当前数字,每选定一个数字就要去看看是不是满足上升的性质,这里第i个阶段就是取思考是否要选择第i个数,第i个阶段有两个状态,分别是选和不选。依稀出现了迷宫找路的影子。每次当我决定要选择当前数字的时候,只需要和之前选定的一个数字比较就行了!这是和之前迷宫问题的本质不同!这就可以纵容我们不需要记录之前所有的状态。既然我们的选择已经不受之前的状态的组合的影响了,那时间复杂度自然也不是指数的了。虽然我们不在乎某序列之前都是什么元素,但我们还是需要这个序列的长度的。所以我们只需要记录以某个元素结尾的LIS长度就好!因此第i个阶段的最优解只是由前i-1个阶段的最优解得到的,然后得到了dp方程。
所以一个问题是该用递推,贪心,搜索还是动态规划,完全是由这个问题本身阶段间状态的转移方式决定的~
每个阶段只有一个状态-------------递推(递归是递推式求解的方法)
每个阶段的最优状态都是由上一个阶段的最优状态得到的----------贪心
每个阶段的最优状态是由之前所有阶段的状态的组合得到的---------搜索
每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管之前这个状态时如何得到的------动态规划
每个阶段的最优状态可以从之前某个或某些状态直接得到,这个性质叫做最优子结构;
而不管这个状态时如何得到的,这个性质叫做无后效性。
另外,其实动态规划中的最优状态的说法容易产生误导,以为只需要计算最优状态就好,LIS问题却是如此,转移时只用了每个阶段的选的状态,但实际上有的问题往往需要对每个阶段的所有状态都作出一个最优值,然后根据这些最优值再来找最优状态。