断断续续磕动态规划已经好几周了,怎么讲,如果非要概括一下这两三周的成果的话,大概就是从入门到放弃……动态规划题虽然没有千千万,但也有典型例题两百题吧,磕了一些网上的教程,磕了geeksforgeeks的dynamic programming,刷了大概40题左右的动态规划题,有些刷了两遍的时候还是得抓耳挠腮,状态怎么定义,状态如何转换。
曾经想过用暴力的方式破解,然后观察其中哪些计算时重复的,然后再对重复计算的东西来进行一个记录。还想到了剪枝之类的,但是往往理想很丰满,现实很骨感,我自己意淫出来的大部分DP的状态定义大部分是错的。很多时候明明得用三维来标记状态,却选了二维,用二维标记选了一维,(但是有时候只需要一维的然后选了二维,结果是对的233333)然后打死都弄不来。而且好多题解用暴力的方式做还真做不来。
持续抗战了前前后后快一个月(虽然中间还有出去玩耍的时候),决定动态规划的抗战到此结束,虽然每次做DP很有自虐或者说是高中学习的快感。怎么讲,首先题目难度在你现有能力往上一点点,不至于难到一步登天,只要花点时间花点心思都能做出来,但是……题目不像BFS或者说DFS那样在一个大致的框架内做不算太大的微小的改变,光是大致的框架就得有好多个,有时候题目啰啰嗦嗦一层套一层,光看懂题目就得费老长一段时间,等定义状态的时候更是云里雾里而且老以为是这样的,其实正确答案又是那样的(This that,comme ci comme ca),然后看完答案之后,长吁一口气,原来是这样的。
虽然很有挑战性,刷起来其实有时候还觉得其乐无穷,但是真的得放弃了,以后会再捡起来,但不是现在,不应该在这上面浪费更多的时间了。
总的来说,动态规划虽然比其他类型的题目灵活,但总体还是有很大一部分的题目都在一定的框架之内,最难的难点是定义各种状态,如果状态定义好,那递推公式也想出来了。(很多时候的思路的过程更多的是先想好递推公式,才能确定定义的状态是对的。)
其实主要的步骤还是
- State
- Function
- Initialize
实现方式有Bottom up,从小到大,还有memorized search,从大问题到小问题。 目前遇到的大部分问题都可以用bottom up的方式解决,(能用bottom up就不用top down了,毕竟bottom up写的多,不容易错)。但是有一类,比如区间型的DP或者search in a 2d matrix, 结果和子结果的相对位置关系不好弄的时候,采用memorized search的方式。
后面是一些简陋的总结吧,主要集中在如何定义状态,以及递推公式这两个步骤。实现上会有一些边界考虑的情况,也挺容易错的,要多举例,或者举反例(我举的反例貌似老是不是反的……)
坐标型动态规划
目前来说,坐标型的动态规划,即左上角走到右下角的这种类型的题目都还算简单,最大,最小,方案个数之类的各种题目。一般都选用bottom up的写法。一般当前答案可能是由上一层/左边得到。典型例题,triangle count,unique path,都是比较简单的坐标型动态规划。总的来说,这类题目大多数还在handle的范围内,除了个别比较变态的题目……
跳跃问题
描述:给出一个非负整数数组,你最初定位在数组的第一个位置。
数组中的每个元素代表你在那个位置可以跳跃的最大长度。
判断你是否能到达数组的最后一个位置。
A =[2,3,1,1,4],返回 true.
A =[3,2,1,0,4],返回 false.
思路:
if(dp[i]==true){
for(int j=1;j<=A[i]&&size;j++){
dp[i+j]=true;
}
}
follow up: 给出最少的跳跃次数
稍微变了一下,但还是很简单,只需要从前往后选一个最小的加1.
dp[i] = min{dp[i-j]}+1 if A[i-j]>=j
骰子求和
扔 n 个骰子,向上面的数字之和为 S。给定 Given n,请列出所有可能的 S值及其相应的概率。坐标类型,走到最后一步。
思路: 开始定义状态的时候只定义了 f[target],只有一维,自以为是对的,然后……其实是错的。 但增加到二维,分别是分值和第几次之后,思路就很简单了。
f[i][j] += f[i-1][j-k] for k in [1,6]
打劫房屋
在上次打劫完一条街道之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子围成了一个圈,这就意味着第一间房子和最后一间房子是挨着的。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。
给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,你最多可以得到多少钱 在不触动报警装置的情况下 [1,2,3,5]
思路:这题其实很简单,分两种情况,拿第一间房子的时候算一遍,结果为f[n-1],不拿第一间房子的时候算一遍,结果为f[n],然后取最大的一个。
交叉字符串
描述:给出三个字符串:s1、s2、s3,判断s3是否由s1和s2交叉构成。比如 s1 = “aabcc” s2 = “dbbca”
-
当 s1 = “aadbbcbcac”,返回 true.
-
当 s3 = “aadbbbaccc”, 返回 false.
思路:看上去像双序列类型的题目,其实是坐标型,因为不能跳过某个字符。一旦不匹配,则为false。