算法基础之递归与动态规划(一)

241 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 递归算法的适用场景

当一个大规模的问题与小规模的问题有着相同的形式,解决大规模的问题和解决小问题的方法是同一个方法时,就可能可以用递归的算法来解决。递归算法的特点为:一个函数递归调用本身(由后向前),通过递归调用来缩小问题的规模,直到组中遇到退出条件,再由前向后组装出目标解。

2. 递归的三要素

  1. 明确函数的目的,确定函数的输入参数和返回值.
  2. 寻找递归的结束条件。
  3. 找出函数的等价关系,也即大规模问题转换为小规模问题的表现形式

3. 递归的缺点

操作系统会给每个进程分配一个最大上限的堆栈空间,如果超过了这个内存空间大小程序就会崩溃(core dump).函数调用的参数是通过栈空间来传递的,在调用过程中会占用线程的栈资源(用来存储函数的参数、局部变量等。但是如果编译器会优化的话好像局部变量不会存在这里)。栈空间一般比较小,往往只有几M。而递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。

此外,递归可能会进行大量重复运算,时间复杂度指数级,导致耗时严重。具体例子见后文。

4. 递归算法的例子

4.1. Fibonacci数列

leetcode-剑指 Offer 10- I. 斐波那契数列 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

4.1.1. 解法一:原始的递归不做任何优化

class Solution {
    public int fib(int n) {
        if(n<2){
            return n;
        }
        return (fib(n-1)+fib(n-2))%1000000007;
    }
}

这种算法在n>=43以后就超出时间限制,这是因为进行了大量重复运算,解释如下: 在这里插入图片描述

图4.1 Fibonacci递归算法求解示意图

由上图可见,大量子问题都进行了重复运算。

4.1.2. 带备忘录的递归算法

为了避免大量重复运算,我们可以用一个数组将一些中间计算结果存起来。

class Solution {
    public int fib(int n) {
        if(n<2){
            return n;
        }
        long[] memo=new long[n];
        return (int)((helper(n-1,memo)+helper(n-2,memo))%1000000007);
    }
    private long helper(int n, long[] memo){
        if(n<2) return (long)n;
        if(memo[n]!=0) return memo[n];
        memo[n]=(helper( n-1, memo)+helper( n-2, memo))%1000000007;
        return memo[n];
    }
}

上述算法其实是对递归生成的二叉树进行了大量的剪枝,如下图所示: 在这里插入图片描述

图4.2 利用数组对递归生成的二叉树剪枝

时间复杂度和空间复杂度都变成了O(n)O(n)。此时的递归算法和动态规划已经很像了,不过递归是先从后往前分解问题规模,再从前往后返回答案。而动态规划一般是直接从前往后根据递推式计算答案。 一般动态规划的场景中往往还有其他限制条件,例如求最优方案,后文将详细介绍。

4.1.3. 利用动态规划解决Fibonacci

从前往后递推,并且可以优化存储空间,只存储最近的两次结果,每次递推迭代时记得更新最近的两次结果变量。

class Solution {
    public int fib(int n) {
        if(n<2) return n;
        int result=0;
        int a=0,b=1;
        for(int i=2;i<=n;i++){
            result=(a+b)%1000000007;
            a=b;
            b=result;
        }
        return result;
    }
}

参考labuladong.gitbook.io/algo/dong-t… 注:图片来源见水印