动态规划实战篇--斐波那契数列

斐波那契数列是最经典的动态规划算法应用实例之一。接下来,将从斐波那契数列入手,学习下基于动态规划求解问题。
问题描述:
斐波那契数列, 是指一个数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,
F(1) = 1,
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。更详细的题目描述和示例可以参考力扣
解题思路:
根据斐波那契数列公式,在数列个数大于2的情况下,后一项的值等于前两项的值的和。也就是说,问题的求解依赖其子问题的求解。显然,这个问题可以使用递归算法解决。但是,也应注意到该递归算法会反复地求解相同的子问题,而不是一直生成新的子问题。而动态规划通过安排问题的求解顺序,确保每个子问题只求解一次(将结果保存起来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算)。所以,也可以使用动态规划求解该问题。
算法实现:
接下来将分别使用递归算法和动态规划算法求解该问题。

朴素递归算法

由于已知知道递推公式,所以可以直接基于递推公式实现算法。对应代码如下:

public int fibInRecursion(int n) {
    
    
    if (n < 2) {
    
    
        return n;
    }
    return fibInRecursion(n - 1) + fibInRecursion(n - 2);
}

动态规划算法

根据斐波那契数列公式,在数列个数大于2的情况下,后一项的值等于前两项的值的和。由于该公式需要反复地求解相同的子问题,所以可以使用动态规划算法减少重复子问题的求解。
在明确可以使用动态规划后,接下来考虑如何实现相关算法。动态规划有两种等价实现方法:带备忘的自顶向下法自底向上法。两种方法得到的算法具有相同的渐近运行时间,仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂性函数通常具有更小的系数。由于对于该问题,其子问题空间中所有子问题都需求解,所以采用自底向上方法(减少递归调用的开销)。明确了算法实现方法选择后,接下来考虑实现。

public int fibInDynamicProgramming(int n) {
    
    
    if (n < 2) {
    
    
        return n;
    }
    // 暂存中间结果
    int first = 0;
    int second = 1;
    int result = first + second;
    for (int i = 2; i <= n; i++) {
    
    
        result = first + second;
        first = second;
        second = result;
    }
    return result;
}

可以看到,直接使用递归算法,其最坏时间复杂度是 O ( 2 n ) O(2^n) O(2n),而使用动态规划算法,其最坏时间复杂度是 O ( n ) O(n) O(n),所需消耗的空间也是在常量范围内。
类似的题目还有: (1) 爬楼梯问题,(2) 使用最小花费爬楼梯问题 ,(3) 不同路径问题

参考

https://leetcode-cn.com/problems/fibonacci-number/ 斐波那契数列
https://leetcode-cn.com/problems/climbing-stairs/ 爬楼梯问题
https://leetcode-cn.com/problems/min-cost-climbing-stairs/ 使用最小花费爬楼梯
https://leetcode-cn.com/problems/unique-paths/ 不同路径

猜你喜欢

转载自blog.csdn.net/wangxufa/article/details/123031454
今日推荐