1-2走台阶问题

1-2走台阶问题

问题描述

假设有n个台阶,每跨一步只能上1阶或者2阶台阶。求总共有多少种走法?

问题分解

假设楼梯共有n级,总共的走法为:f(n)
当n = 1时的走法为: (1)。f(1) = 1
当n = 2时的走法为: (1,1),(2)。f(2) = 2
当n >= 2时,第一步有两种选择,可以选择走1级或者2级,当选择1级时,走完第一步后,还有n-1级,此时的走法为f(n-1)。当选择2级时,后面还有n-2级,此时的走法为f(n-2),那么总共的走法f(n)=f(n-1) + f(n-2)
因此递推公式为:f(n)=f(n-1) + f(n-2),结束条件是我们已知的f(1)=1, f(2)=2

因此使用递归的方法求解为:

/*
 * 1-2
 * use recursion
 */
long stairs(int n)
{
    if (n == 1) {
        return 1;
    } else if (n == 2) {
        return 2;
    } else {
        return stairs(n -1) + stairs(n - 2);
    }
}

使用动态规划优化

当n比较大时,会发现上面的方法会很慢,比如当n=50,在我的电脑上要运行数分钟之久。
通过分析上面代码发现,return stairs(n -1) + stairs(n - 2);, 在这一步分别递归调用stairs(n-1)stairs(n-2),其中直到stairs(n-1)调用返回才调用stairs(n-2),但是通过分析发现stairs(n-1)又可以分解成stairs(n-1-1) + stairs(n-1-2), 也即stairs(n-2) + stairs(n-3),所以stairs(n-2)在调用stairs(n-1)的过程中已经计算过了,同理除了stairs(n-2)还有大量的重复计算,如果递归的过程中将所有结果存储下来,那么将节省大量时间。
这是典型的用空间换时间的策略:

/*
 * 1-2
 * use dp
 */
long stairs_dp(int n, long *counter)
{
    if (n <= 0)
        return 0;

    if (counter[n]) {
        return counter[n];
    } else {
        if (n <= 2) {
            counter[n] = n;
        } else {
            counter[n] = stairs_dp(n -1, counter) + stairs_dp(n -2, counter);
        }
        return counter[n];
    }
}

进一步优化

进一步分析,发现n级的计算结果等于n-1级和n-2级计算结果之和,这不就是斐波那契数列?
因此,我们只要知道1级和2级的计算结果,那么n级的结果就可以通过1-n的循环计算出来

/*
 * 1-2
 * us dp with loop
 */
long stairs_dp_loop(int n)
{
    if (n <= 0) {
        return 0;
    }

    long step1 = 0;
    long step2 = 1;
    long step3 = 0;
    for (int i = 1; i <= n; i++) {
        step3 = step1 + step2;
        step1 = step2;
        step2 = step3;
    }
    return step3;
}

总结

1-2走台阶问题是典型的动态规划问题,解决问题的关键就是找到递推公式,即将问题的求值过程分解成数个更小问题的求值,最后将这些小问题的求值结果合并得到最终问题的求值结果;而这些小问题的求值又可以进一步分解,只到分解成简单的终点值(如果1级、2级台阶,我们看到能直接说出答案)。
得到递推公式以及终点值(结束条件),那么我们直接写递归函数就可以求得结果。
然而由于递归函数不注意剪枝,中间有大量的重复计算,因此可以采用空间换时间的方式进行优化,将中间计算结果保存起来,后面通过查表直接得到结果而不用重复计算。
有递推公式和终点值,前面我们都是从问题一步步往前递推直到终点值,然后再从终点值回溯到问题。那么我们是否可以直接从终点值反过来往问题来一步步求解呢?由此有了后面的通过循环来计算结果的方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值