vlambda博客
学习文章列表

动态规划——从入门到放弃

介绍这个算法的原因是今天在刷一道字符串匹配的算法题时,碰到了需要用它来解决,但是老师讲的又搞忘了(其实是没有仔细听),所以就重新学了一道,结果还是没有把这道题想明白搞出来,淦。所以只有简单描述下。

动态规划算法(Dynamic Programming,DP)介绍:

通过查看了许多资料后,我个人总结了一个很容易理解的一种阐述:就是遇到一个大的问题需要解决时,这个大的问题又可以继续切分成许多个级别的小的问题来逐一的去解决,最主要的一点是,在切分的许多个不同级别小的问题之中,包含着即将解决的次一级的小问题的解。(这个小问题的级别的意思,举个例子,比如你要去食堂吃饭,“去食堂吃饭”便是你要解决的一个大问题,而去食堂吃饭之前你要先走到“去食堂的路上”,这是一个次一级的问题,去食堂的路上肯定会“走路”吧,所以走路就是这个次一级问题的解,然后在去食堂的路上你肯定还要“下宿舍楼”吧,而“下宿舍楼”又是一个次次一级的问题,同样下宿舍楼也要“走路”,走路也就是这个次次一级问题的解....这样可以推到你睁眼睛的那个状态)

这类题的特点:1.求最值:比如求最大路径  2.计数:从一端出发有多少种方式走到另一端,有多少种方法来求组合方式   3.存在性 :两人报数到某个数值,最后报到这个数值的人赢,问怎样才能赢...   4.还要很隐形的题目,不容易发觉,比如字符串正则匹配。

这类题的特点:1.求最值:比如求最大路径  

2.计数:从一端出发有多少种方式走到另一端,有多少种方法来求组合方式   3.存在性:两人报数到某个数值,最后报到这个数值的人赢,问怎样才能赢...  4.还要很隐形的题目,不容易发觉,比如字符串正则匹配


一个例题:

你有面值为2元,5元,7元的货币足量,需要买一本书,售价为27元,老板要求你用最少的货币张数来结清书的价格。

这道题意思就是用【2,5,7】这3个数来凑27,并且在所求的凑数组合里,选出长度最小的那个组合。比如【2,2,2,2,2,2,2,2,2,2,7】和【5,5,5,5,7】这两个是题的组合,那么这两个中选择第二个长度最短的组合。

那么利用动态规划如何解决?

  1. 确定状态:首先确定一个状态数组dp[i]或dp[i][j],dp为Dynamic Programming的缩写。

  2. 找最后一步,向前推子问题

    比如这道题里,凑27必然需要27=A1+A2+....+Ak,这样k个面值为【2,5,7】的数,尽管现在我们还不知道k是多少。

    所以也必然存在最后的一个面值Ak的货币,而它前面有面值之和为27-Ak的k-1张货币。

    这里有两个重要的地方:

    1).我们确认前面k-1个货币是绝对能够拼出来的,但是我们先不关心他的拼法(可能1个,2个...). 2).我们要保证前面的k-1个货币是最优的,在本题中也就是货币最少。

  3.推子问题

除去Ak这个次问题,那么“最少用多少张货币才能拼出(27-Ak)面值”便是一个次次问题(比较下原问题,最少张数拼27面值),所以这样一层层向下推,那么“最少用多少张货币才能拼出(27-Ak-A(k-1))面值”便是一个次次次问题...直观一点画个图:

于是,我们可以用X表示将要拼的面值,状态F(X)=最少要用多少张拼出X

现在有了状态函数,我们回到最后一步,最后一步无论如何都会存在一个Ak,所以Ak的情况有三种,Ak=2,Ak=5,Ak=7.

当Ak=2时,它下一个次问题是“如何用最少张数拼27-2”,此时写作状态是F(27-2)

当Ak=5时,它下一个次问题是“如何用最少张数拼27-5”,此时写作状态是F(27-5)

当Ak=7时,它下一个次问题是“如何用最少张数拼27-7”,此时写作状态是F(27-7)

而我们也必须在这三种情况里面再求最优解,也就是找到最小组合,所以要拼成27的最优解,状态可以写为:

F(27) = min{F(27-2)+1 ,  F(27-5)+1  ,  F(27-7)+1},加1是因为除去了Ak这个面值的货币,最后算的时候还要再加回来。

4.求出状态转移方程

F(27) = min{F(27-2)+1 ,  F(27-5)+1  ,  F(27-7)+1}

5.确定初始条件和边界情况

很显然,当初始状态X=0的时候,只有0才能拼出0,F(X)=0,当然,在这个问题中相当于拼结束了。

其次考虑,当X为负数怎么办,比如F(-1),F(-2)这样,肯定不会让老板倒给你钱,所以这种状态是不存在的,记作F(负数)=∞

那么可以开始拼了:

记:F(X)=最少要用多少张拼出X

F(X)=∞表示拼不出X

那么从最最小级别的状态往大推,为什么要从最小级别来推,是因为每个大级别的问题都和一个次级别的问题有部分相同的解,你先求出次级别的问题的解,那么就可以直接将此值带到大级别问题中,而不用再去算一遍。从而减少了时间复杂度。

从F(负数)=∞  ----> F(0)=0  --->  F(1)=min{F(1-2)+1,F(1-5)+1,F(1-7)+1}=∞  ---->  F(2)=min{F(2-2)+1,F(2-5)+1,F(2-7)+1}=1  --->.....--->  F(27)=min{F(27-2)+1,F(27-5)+1,F(27-7)+1}=5

这样就推出来了,时间复杂度O(n)=27 * 3  。补充:动态规划是难,有点抽象,而且有些题还不容易看出来,还要要掌握思想吧。