剑指offer 学习笔记 递归和循环

很多算法都可以用递归和循环两种不同方式实现,通常基于递归的实现方法代码会比较简洁,但性能不如基于循环的实现方法。

递归是在一个函数内部调用这个函数自身。循环是通过设置计算的初始值及终止条件,在一个范围内重复运算。如求1+2+…+n:

#include <iostream>
using namespace std;

int AddFrom1ToN_Recursive(int n) {
    return n ? (n + AddFrom1ToN_Recursive(n - 1)) : n;
}

int AddFrom1ToN_Iterative(int n) {
    int sum = 0;
    for (int i = 1; i <= n; ++i) {
        sum += i;
    }
    return sum;
}

int main() {
    cout << AddFrom1ToN_Iterative(50) << " " << AddFrom1ToN_Recursive(50) << endl;
    return 0;
}

递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。以上例子中,递归效率不如循环。除了效率外,递归还有可能引起更严重的问题:调用栈溢出,当调用的层级太多时,就会超出栈的容量,造成栈溢出。

通常应用动态规划解决问题时我们都是用递归的思路分析,但由于递归分解的子问题中存在大量重复,因此我们总是用自下而上的循环来实现代码。

面试题10:斐波那契数列。(1)写一个函数,输入n,求斐波那契数列的第n项。

很多人会用递归来解:

#include <iostream>
using namespace std;

int Fibonacci(int n) {
    if (n <= 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}

int main() {
    cout << Fibonacci(2) << endl;
    return 0;
}

但这种解法效率极低,当我们求解f(10)时,我们需要先求f(9)和f(8),在求解f(9)时,需要求解f(8)和f(7),分析后不难发现,很多值我们求了多次(如上例的f(8)),而且重复的计算会随着n的增加急剧增大。用递归方法计算的时间复杂度是以n的指数方式递增的。

改进方法:从下往上计算,时间复杂度为O(n):

#include <iostream>
using namespace std;

int Fibonacci(int n) {
    int result[] = { 0,1 };
    if (n <= 1) {
        return result[n];
    }

    int a = 0, b = 1, FibN;
    for (int i = 0; i < n - 1; ++i) {
        FibN = a + b;

        a = b;
        b = FibN;
    }
    return FibN;
}

int main() {
    cout << Fibonacci(4) << endl;
    return 0;
}

还有一种时间复杂度为O(logn)的解法,这个解法需要用到一个公式:
在这里插入图片描述
有了这个公式,求出方阵的n-1次方即可。如果简单地从0开始循环,n次方需要n次运算,时间复杂度仍为O(n),但我们可以考虑当n-1为偶数时,只需要求(n-1)/2次方的平方即可,这样可以用递归的思路实现。

(2)青蛙跳台阶问题。一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。

首先考虑最简单的情况,如果只有一级台阶,只有一种跳法;如果有两级台阶,有两种跳法。当台阶数为n时,青蛙能从第n-1阶台阶或n-2阶台阶跳上来,此时跳法数等于f(n-1) + f(n-2),这就是斐波那契数列了。

在青蛙跳台阶的问题中,如果条件改成一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,…他也可以跳上n级台阶,此时青蛙跳上n级台阶的方法数为2n-1

f(n) = f(n-1) + f(n-2) + ... + f(1) + 1
f(n-1) = f(n-2) + f(n-3) + ... + f(1) + 1
f(n) - f(n-1) = f(n-1)f(n) = 2f(n-1)
又因为f(1) = 1,因此方法总数为2^(n-1)

在这里插入图片描述
以上是一个2*1的矩形和一个2*8的矩形,求用8个2*1的小矩形无重叠地覆盖2*8的大矩形时,有多少种方法。

我们可以横着覆盖或竖着覆盖,当我们竖着覆盖最左边时,右边还剩下2*7的矩形,这种情形下覆盖方法记为f(7);当我们横着覆盖左上角时,左下角也就确定是横着覆盖,右边还剩下2*6的矩形,此时覆盖方法数为f(6)。因此f(8)=f(6)+f(7),仍然是斐波那契数列问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值