从最长回文串到贪心和动态规划(2)

上一篇,是关于最长回文子串的Manacher算法的详解,这篇,我们进入动态规划的世界。

https://blog.csdn.net/u013309870/article/details/75193592

动态规划  Dynamic Programming:

下面一句话 和 一段对话就能说明动态规划的本质:记住已经解决过的子问题的解

那些记不住过去的人注定要重蹈覆辙

A * "1+1+1+1+1+1+1+1 =?" *

A : "上面等式的值是多少"
B : *计算* "8!"

A *在上面等式的左边写上 "1+" *
A : "此时等式的值为多少"
B : *quickly* "9!"
A : "你怎么这么快就知道答案了"
A : "只要在8的基础上加1就行了"
A : "所以你不用重新计算因为你记住了第一个等式的值为8!动态规划算法也可以说是 '记住求过的解来节省时间'"

只要在8的基础上再+1就知道答案了。

求解的方式有两种:①自顶向下的备忘录法 ②自底向上。 

下面我将通过例子,尽可能以最清晰的方式将DP问题解释清楚,读者若跟着我的思路走,必将把DP安排的明明白白。


从Fibonacci讲起:

在牛客网刷剑指offer时,一定会遇到几个问题,Fibonacci,跳台阶,变态跳台阶等等,用的是递归解法,我们来看看递归方式到底如何转变为DP解法。

fib:  1 1 2 3 5 8 13 21 34....
index: 0 1 2 3 4 5 6  7  8.... 
def fibonacci(n):
    if n < 0:
        return False
    if n == 0:
        return 1
    if n == 1:
        return 1
    return fibonacci(n-1) + fibonacci(n-2)

咱们分析一下上面递归解法的问题。下面图希望读者手动画一下,加深记忆:

由图可以看出,出现了大量的重复计算,这才只到6就这样了,想想n如果很大,会有多少重复计算!

这时候大多数人一定会想到:记住中间结果啊,那样就可以避免重复计算已经计算过的值啦,这种思路,就对应我们第一种动态规划方法:

1. 自顶向下的备忘录法:

def fibonacci_memery(n):
    if n <= 0:
        return n
    memery_arr = [-1] * len(n+1)
    return fibonacci(n, memery_arr)

def fibonacci(n, memery_arr): 
    if memery_arr[n] != -1:
        return memery_arr[n]        
    if n <= 2:
        mermery_arr[n] = 1
    else:
        memery_arr = fibonacci(n-1, memery_arr) + fibonacci(n-2, memery_arr)
    return memery_arr[n]

备忘录法也是比较好理解的,创建了一个n+1大小的数组来保存求出的斐波拉契数列中的每一个值,在递归的时候如果发现前面fib(n)的值计算出来了就不再计算,如果未计算出来,则计算出来后保存在Memo数组中,下次在调用fib(n)的时候就不会重新递归了。比如上面的递归树中在计算fib(6)的时候先计算fib(5),调用fib(5)算出了fib(4)后,fib(6)再调用fib(4)就不会在递归fib(4)的子树了,因为fib(4)的值已经保存在Memo[4]中。

2. 自底向上的动态规划

不知大家是否看出来,上面的备忘录法虽然是由一个数组保存了中间结果,但是我们计算fibonacci(6)的时候也还是要计算出前面的1,2,3..,我们为什么不能先计算出来1,2,3..呢?

这才是动态规划的核心!!!先计算出来子问题,由子问题来计算出最终的父问题。

def fibonacci(n):
    if n <= 0:
        return n
    memery_arr = []
    memery_arr[0] = 1
    memery_arr[1] = 1
    for i in range(2, n+1):
        memery_arr[i] = memery_arr[i-1] + memery_arr[i-2]
    return memery_arr[n]

自底向上算法也是利用了数组保存先计算的值,为了后面的调用服务,上面代码中参与循环的只有i,i-1,i-2三项,我们可以降低辅助数组的空间复杂度。

def fibonacci(n):
    if n <= 0:
        return n
# 好好体会一下下面代码    
    memo_i_2 = 0
    memo_i_1 = 1
    memo_i = 1
    for i in range(2, n+1):
        memeo_i = memo_i_2 + memo_i_1
        memo_i_2 = memo_i_1
        memo_i_1 = memo_i
    return memo_i

一般来说备忘录方式的DP使用了递归,递归就要产生额外的开销,使用自底向上的DP方法要比备忘录法好!


你以为你懂了DP?too young too simple!

菜鸟级:钢条切割问题:

è¿éåå¾çæè¿°

也就是收益最大问题!

下图进行了分析,如何找到收益最佳的解!(是不是划分了所有的子问题,子问题的最优解构成了原问题的最优解--DP)

è¿éåå¾çæè¿°

最优子结构:

问题的最优解由相关子问题的最优解组合而成!而这些子问题也是可以独立求解的!

其实除了上述求解子问题最优解的方法之外,还有更容易理解的:递归

我们将钢条从最左边割下长度为i的一段,只对右边n-i的一段继续进行切割(调用自身,递归求解),对左边的一段不再进行切割。问题的分解方式为:将长度为n的钢条分解为左边开始一段以及剩余部分继续分解的结果。

 

猜你喜欢

转载自blog.csdn.net/mr_xiaoz/article/details/81268656