【DP】笔记一

【DP】笔记一

摘要

动态规划方法通常用来求解最优化问题(optimization problem).这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值(最小值或最大值)的解。我们称这样的解为问题的一个最优解(an optimal solution),而不是最优解(the optimal solution),因为可能有多个解都达到最优值。

我们通常按照如下4个步骤来设计一个动态规划算法:

  1. 刻画一个最优解的结构特征。
  2. 递归地定义最优解的值。
  3. 计算最优解的值,通常采用自底向上的方法。
  4. 利用计算出的信息构造一个最优解。

上面这一段是从《算法导论》抄来的,做DP问题一般都会按照这个思路去搞。看起来很容易,但是我最怕的就是DP问题(当然,并不是其它问题不怕…),为了缓解一下这种心理阴影,准备把LeetCode里的DP问题刷完。DP不归路刚刚开始,遇到两个有阴影的问题,都是看完提示才搞定的,遂记之。

140 Word Break II

题目链接Word Break II。初看题目,就感觉子结构体不太好划分,后面果然是卡在这上面。先后试过几种算法。

1. 递归算法

​ 好像DP的问题很容易就会套用递归算法去解。这题也不例外,因为最优子结构一直抽象不出来,所以先从递归 算法开始找感觉。传入字符串,从字典中查找,如果有匹配的词,则递归子串。很简单,当然,不出意外, TIME OUT.

2. 加上备忘

​ 对于长的字符串,递归后的回溯检查,会有大量重复的计算。因此考虑,加上备忘,提高一下回溯的效率。在每次递归成功后,将当前的子串和字典的组合存下来,后续再次碰到同样的子串后,直接用对应的字典组合来生成结果。速度提升非常明显,直到遇到aaaaaaaaaaaabaabaaa…类似这种用例,就是重复的词重复出现,看到这个第一反应是KMP算法,但是KMP的使用场景是查找,明显不适用。这种情况因为备忘也会存很多,所以也会导致速度变很慢。显然用例是故意这么设计的,因为输出为空,用意就是检查遇到无法再继续匹配的情况,算法的处理。在这个地方卡住了,所以又换了个思路。这次绕不开DP了,就只能往DP的思路上去靠。(其实这两种算法在实现前我就预感到问题的存在,但是这并不值得骄傲啊。。。)对于重复子结构,我犯糊涂了,对于s[i,j]这种结构的抽象,总是得不到合理的方案,感觉距离成功只差一步。(哪一次卡壳不是呢,呵呵)直到看到了提示。。。

3. DP算法1

​ 遍历输入字符串,在循环中,遍历字典,只有后向匹配成功,才从成功的位置进行组合,备忘就是到每个位置的字典组合。就是这么简单。。。不像之前考虑的[i,j]之间要怎样怎样,欲哭无泪啊。。。不过,依然TIME OUT,不要吃惊,原因跟上面的算法2一样,对于重复词的场景,虽然结果为空,但中间过程备忘太大,影响效率。到了这步,当然无须再看什么提示了。。。

4. DP算法2

​ 算法框架如上,备忘换成匹配成功的位置;在算法完成后,再根据位置对结果进行组合。该算法很好的解决上面的问题,因为中间过程的备忘不需要拷贝。。。

174 Dungeon Game

题目链接Dungeon Game。初看该题的感觉,跟上题不太一样,就是感觉会比较曲折,但是应该能搞定的那种,谁知打脸来的太快。。。

1. 递归算法

对于我不能解决的DP问题,我是不惮以递归算法来解决的。。。两个分支,向下向右,然后TIME OUT。但是,这并不妨碍心理上会有某种自我安慰。。。

2. DP自顶向下

​ 因为分支是固定的,只能向下和向右,所以,结构划分是确定的;问题在于最优解的定义。本题难点在于求解所有路径的最小生命初值的最小值。这一点对于递归算法不是问题,因为递归会遍历所有路径,并存下每条路径的最小生命初值,最终通过比较会得到一个最小值。但是DP自顶向下算法中,对于位置(i,j)的情况,在该位置可以获取从(0,0)到该位置的最小生命初值,但是由于需要继续往下计算,还需要记录路径的当前生命值(也就是从其实位置到该位置的累加值),那就存在一个问题,需要记录哪条路径的当前值?

  1. ​ 当前位置最小生命值的路径值? NO,如果这都可以,那就是贪心了,本题当然不是

  2. ​ 记录所有路径的值? NO, 如果这都可以,这还叫DP?

  3. ​ 当前位置最大的路径值? NO,如果这都可以。。。

  4. ​ 记录最小生命值和最大路径值两条? NO,原因就是1、3的原因

  5. ​ 记录当前位置到右侧和下侧的最小生命值? NO, 原因跟1没啥区别,没有考虑到后续路径变化的动态过程。

  6. ​ 卡壳。。。

    这题比上题更阴影,对递归算法都没法改进。。。

3. DP自底向上

​ 看到提示后,第一反应是踢自己两脚,为啥自己没想到,然后才想为毛自底向上可以,自顶向下不行?(奇怪的人类) 本人水平有限,简单证明一下:

​ 自底向上算法:求位置(i,j)处的最小生命值,只依赖(i+1, j) 和 (i, j + 1)两个位置的最小生命值以及(i,j)本身,所以递推(0,0)处的值也是可以按此方法求出的。

​ 自顶向下算法:求位置(i,j)处的最小生命值,见上面的分析,依赖所有的路径,没有办法抽象出最优解。

可能感觉上面的证明跟没说一样,那就贴段自底向上算法最小生命值的求解方法,供意会。

if (i + 1 < dungeon.length) {    
	down = new Node();    
	down.cur = memo[i + 1][j].cur + dungeon[i][j];    
	if (memo[i + 1][j].cur_min >= 0) {        
		if (dungeon[i][j] < 0) {            
			down.cur_min = dungeon[i][j];        
		} else {            
			down.cur_min = down.cur;        
		}    
	} else {        
		down.cur_min = dungeon[i][j] + memo[i + 1][j].cur_min;    
	}
}						

总结

​ 例行总结时间:从174这道题目上看,自底向上和自顶向下对于算法还是很不一样的,这一点颠覆了我的认知,之前一直认为只是同一种算法的不同实现;140这道题目,说到底还是脑筋有点僵化,对于重复子结构的划分总是拘泥于[i,j]内循环遍历这种模式。好啦,DP不归之路长路漫漫,慢慢耍啦。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章