什么是动态规划?为什么它比递归强? 只需要一个案例,就能讲清楚!

我记得当年刚接触到动态规划的时候,就摸不清其精髓。很多时候,看到一道动态规划题的解法,感觉自己好像懂了。但之后再尝试做一下,依旧不会用。我相信大家可能有类似的经历。所以,想借这个机会,给大家讲一下这个算法中的屠龙宝刀 - 动态规划。其实,只需要懂一个案例,就能明白它的精髓。

以下是leetcode的一道经典动态规划题目,爬楼梯。我相信大伙儿不少也碰见过。所以,我希望通过我的讲解,可以让大家更清晰得把握动态规划的本质。大家对递归都有接触过。递归的本质是什么?就是说能把一道大题拆解成类似的小题。因此,我们采取递归的形式,先把小题解决了,然后把这些小题组合起来,从而把大题解决了。经典的递归应用案例有:mergesort, fibonacci numbers,或者是汉诺塔。

据我了解,几乎所有的动态规划问题,都能被递归解决。所以很多人要问了,为什么我们需要用到动态规划呢?本质上,基础递归并不区分小题的类型,所以自然而然,有不少小题是重复的,所以有重复运算。比如拿fibonnaci numbers 来举例。当我们尝试着用递归把fib(n) 分解成 fib(n-1) 和 fib(n-2), 使得fib(n) = fib(n-1) + fib(n-2)。之后,我们尝试分解 fib(n-1) = fib(n-2) + fib(n-3) 时,我们就重复运算了一遍fib(n-2)。设计算法时,时间复杂度很重要,所以,我们不希望重复运算。因此,如何解决这个问题?很简答,我们只需要把fib(n-2) 储存起来就行了。因此,动态规划运用了储存之前对于小题目运算值的这个机制,来避免之后对类似小题目的重复运算。这就是递归和动态规划的本质区别。简而言之,动态规划是在递归思想的基础上,加上了保存运算小题目的结果。

回归到这道爬楼梯的题目。我们可以选择用递归的方式去解决。但是,假如大家仔细地想一下,就会发现,我们会重复计算低层楼梯的走法,就像我刚才提到用递归解决fibonnaci numbers。因此,我们需要用动态规划来加快我们的运算。把之前算过低楼梯层的结果保存,用于计算之后楼梯层数的全部走法总和。下面有我写得算法代码以及对它的分析,有兴趣的盆友可以看一下。

动态规划可以运用于很多场景,并且有时候,把握住写动态规划的一些小细节还是比较复杂的。但是说到底,动态规划的核心还是通过保存数据,来避免对类似题目的重复运算。我希望大家记住这点,在碰到有这样场景应用的情况下,可以适当的去用(比如一些可以用递归解决的问题)。

Problem: 70. 爬楼梯

思路

看到这道题,我就想到了用算法中的屠龙宝刀-动态规划。几乎任何的递归题都可以用动态规划解决。这道题,自然而然有递归的形式。打个比方,如果要爬到四楼,我门则可以从3楼,爬一层,或者2楼爬两层,或者从2楼爬两次一层。我们只需要用counter variable 保存之前走上前面两个台阶的走法,让后把它们加在一起,运用recursive leap of faith, 就行了。

解题方法

我们只需要把前面两个楼梯的全部走法保存,则当前楼梯的走法,则是前面两个楼梯走法的总和。为何这种方法正确呢?我们可以用递归的形式来证明。我来提供一个informal的证明。假如我们知道前面两个楼梯的全部走法,则走当前这个楼梯,我们可以走两步,或者走一步。因此,这种解法是合理且正确的。

复杂度

时间复杂度:

O ( n ) O(n) O(n), 因为我们需要遍历一遍所有的楼梯层,到达最后楼梯层n。

空间复杂度:

O ( 1 ) O(1) O(1), 只需要几个counter variable 来保存之前走过楼梯的走法综合就行了。

Code

class Solution {
public:
    int climbStairs(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        int prev_prev = 1;
        int prev = 2;
        int cur = 0;
        for (int i = 3; i <= n; i++) {
            cur = prev_prev + prev;
            int tmp = cur;
            prev_prev = prev;
            prev = tmp;
        }
        return cur;
    }
};
  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值