递归和分治区别,动态规划和递归之间的关系是什么?

题主勿须担心,思维方式特别习惯某种方式很正常。

DP说白了就是一个解决问题的思路——即一个大一点规模的问题可以被拆解为更小的,更容易解决的问题。

拿最简单的斐波那契问题举例子,一个大的问题f(n)可以被拆解为小一点的问题f(n-1)和f(n-2),……直到然后拆到最小的问题f(1)和f(2)。你可以从f(n)从大往小了算,也可以先从f(1), f(2), f(3)……往大了算。再比如leetcode的有个正则表达式匹配的问题,你可以把问题看做是一个大的字符串的匹配pattern,拆解为字符串的一部分匹配pattern的一部分的问题;也可以反过来先匹配一小部分,再不断让可以匹配的范围变大。

很多人把从大往小算的形式称作递归,反过来从小往大了算称为DP。但实际上只要满足大规模问题可以拆解为小规模问题,这个思路本身就是DP的,无非是顺序不一样罢了。

所谓【动态规划】就是指知道了一组小规模问题的答案后,就可以用一个方案(状态转移方程)组装成大一点规模问题的答案的做法而已。为啥叫“动态”呢,因为状态转移会和几个条件相关,而不是一开始就可以无脑写死(无脑写死的一般就是贪婪)。

术语上,从大了往小算被称为”自顶向下“,而从小往大算被称为”自底向上“。尽管自顶向下和自底向上等价,但自顶向下算一个很容易发生的问题就是重复计算,比如你为了算f(5),普通的递归方式要重复计算很多很多次f(3), f(2)……。这些计算都是浪费的,实际表现比自底向上差的多(但再强调下,二者的思路没本质差别,仅仅是计算浪费不浪费的问题)。但你可以加cache,每次递归的函数的参数都可以组装成一个cache的key。这样每次递归时,可以先检查一下cache是不是有,有了就不用算了,直接返回。这种带cache的递归DP一般被称为“记忆化搜索“。记忆化搜索与自顶向上的DP计算方式在算法复杂度上理论上是一样的。但cache一般会用map,其实际的复杂度要比直接用数组的自顶向上要大,二者的O(1)是不一样的。此外记忆化搜索还会引入函数调用的开销,所以一般记忆化搜索比等价的自底向上要慢那么一丢丢。

此外自底向上的计算方式还可以优化存储,比如斐波那契计算时,计算f(n)只需要f(n-1), f(n-2),所以用两个变量就行,无须把所有的结果都记录下来。用go写大概这样:

func fib(n int) int {f0, f1 := 0, 1for n > 1 {f1, f0 = f0 + f1, f1n--}return f1}

但用记忆化搜索的方式,你是没法在计算得到f(4)后,把f(1)结果占用内存给方便的清理掉的。

DP最难的不在于自底向上还是自顶向下,而在于怎么拆问题。某些问题,如果之前没做过,你是很难想得到“这个问题还能这么拆”。不信?可以去看看LeetCode有一道“戳气球”的题目。我自己第一次看时虽然能猜到它是用DP,但打死都想不出来怎么拆,直到看了题解。因此大家想不出来DP,不是因为不知道DP的代码框架怎么做,而是拆解问题的思路实在太古怪,太反常识。这就需要刷题去多多适应。

But,这些反常识的方法也是人想出来的。此时也是去了解那些聪明人的脑洞的好机会:)。你也许会惊讶于人思维方式的差异的巨大。

在现实工程中,有些问题即便是可以DP,但因为拆解方案对于大部分人来说太难想;或者即使想出来,应用面太窄,需求小小的一个变化就会让整套方案不灵了。因此很多时候,如果复杂度的要求不那么严苛,还是用暴力要好一些。因此DP更常见于面试题里,证明候选人“知道像计算机专业从业者那样思考”。但这并不是说DP不重要,在计算机科学里,这个思路非常非常重要,只不过工程上不光要考虑算法复杂度,还要考虑可维护性问题而已。如果场景恰当,当然还是要用。比方说,我们之前做的“计算基金最大回撤”就用DP,实际上就是LeetCode里“买卖股票最佳时机”解题方案的镜像——就是一个算回撤,一个算正向收益的区别。

回到DP本身,题主不妨试试这样做。既然题主的思维方式觉得自顶向下很舒服,那就总是先用递归实现自顶向下DP。通过后加上Cache用记忆化搜索的方式写代码。再改写成等价的自顶向上的做法。反复做几次看看思路上能不能贯通。但同时,我也遇到过写自底向上很舒服,反过来就别扭的人。

当然,虽然二者等价,我依然遇到过很难自底向上写的问题。此时一般是很难找到一个很简单的从小到大的规律,这就不如用递归+cache。cache的规则就很简单,算过了就cache,没算过就没cache,递归调用中参数出现的规律不需要操心。

同时,也有反过来的情况,自底向上很难写。主要是这类问题的递归方式相对好理解,但自底向上时就要让dp[i][j]表达一些比较模糊,很难说清楚的概念,让编码变得很别扭。所以遇到这种情况就用容易理解的形式写就好。

最后,递归能够在DP这个思路里支持实现自顶向下,但也不一定就用在DP里。递归适合的场景要更加广泛。比如dfs/bfs这样的搜索场景就可以用递归做。递归还可以用来做模拟,比如A对B做一件事、B又要对C和D做一件事,D又要对A和B做一件事……。用递归来实现很直观。

上海迪士尼入住酒店包不包含门票 上海迪士尼在建园区 上海迪士尼小女孩大便 上海野迪士尼在什么区别 上海迪士尼芙苓粉 迪士尼游玩攻略一日游上海 上海迪士尼乐园建设总规划 上海迪士尼怎么合作票务 上海惠民节迪士尼半价 上海迪士尼里面有全家便利店吗 从酒泉怎么去镜铁山 从王哥庄去金顶怎么坐车 从北京到江原道怎么去 从北京到武城怎么去 从宝安机场怎么坐车去深圳东站 从地铁东湖渠去西客站怎么走 从镇宁到贵阳息烽怎么去 从黄河道去河东区万达广场怎么走 从深圳李朗去东莞高埗怎么走 怎么从微信保存视频保存不到相册里面去