算法:动态规划思路(仅作记录)

以leetcode70题爬楼梯为例:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

递归
一共两种爬楼梯的方式
如果最后一步要用到方法1,那么我们得先爬到 n−1,要解决的问题缩小成:从 0 爬到 n−1 有多少种不同的方法。
如果最后一步要用到方法2,那么我们得先爬到 n−2,要解决的问题缩小成:从 0 爬到 n−2 有多少种不同的方法。
从 0 爬到 n 的方法数等于这两种方法数之和,即
f(n) = f(n-1) + f(n-2)

int climbStairs(int n) {
     //n=1时,不存在n-2,因此不带入f(n)
    if(n==1){ 
        return 1;
    }
    return f(n);
}
int f(int n){
    if(n==0 || n==1){
        return 1;
    }
    return f(n-1) + f(n-2);
}

递归+记忆化搜索
递归时有重复大量计算的情况: 如f(5)=f(4)+f(3),f(4)=f(3)+f(2),使用列表保存第一次计算f(n)的值,后续如果需要再计算f(n)时直接返回,不进入后续递归,记录下计算f(4)时f(3)结果,在计算f(5)时直接返回=f(4)+cache[3]。
时间复杂度:O(n),需要在每个节点第一次出现时计算,一共n次。

vector<int> cache;
int climbStairs(int n) {
    if(n==1){
        return 1;
    }
    cache.resize(n+1, -1);
    return f(n);
}
int f(int n){
    if(n==0 || n==1){
        return 1;
    }
    if (cache[n] != -1){
        return cache[n];
    }
    int res = f(n-1) + f(n-2);
    cache[n] = res;
    return res;
}

递推(自底向上)
在递归时我们为了使f(n)到达我们已知的递归边界f(0)与f(1),将f(n)自顶向下拆分为子问题,又将子问题的结果归上去(有种折返跑的感觉)。
所有我们可以使用递推,直接从边界开始累加到f(n),运行速度显著快于递归。
时间复杂度:O(n)
空间复杂度:O(n),创建了长度为n+1的列表

int climbStairs(int n) {
    vector<int> f(n+1, 1);
    for(int i=2;i<n+1;i++){
        f[i] = f[i-1] + f[i-2];
    }
    return f[n];
}

递推+空间优化
实际上在递推的过程中我们只使用了,三个变量,f[i], f[i-1]与f[i-2],对于列表f,每次计算时,在f[i-2]之前所保存的值再也不会被使用,实际上它们已经包含在由它们累加而来的f[i-1]和f[i-2]中了,因此可以直接使用这三个变量替换列表。

int climbStairs(int n) {
    // vector<int> f(n+1, 1);
    int f_1 = 1, f_2 = 1, new_f = 0;
    // f_1上一个,即f[i-1],f_2上上一个即f[i-2],它们第一次出现时,为f(1)与f(0)=1
    for(int i=2;i<=n;i++){
        // f[i] = f[i-1] + f[i-2];
        new_f = f_1 + f_2;
        f_2 = f_1;
        f_1 = new_f;
    }
    return new_f;
}
posted @ 2024-09-18 23:08  lanying24  阅读(5)  评论(0编辑  收藏  举报