LeetCode刷题复盘笔记—一文搞懂509. 斐波那契数70. 爬楼梯以及递归时间复杂度计算方法(动态规划系列第一篇)

今日主要学习了动态规划算法,AC 了两道动态规划题目,之后主要研究了一下递归的时间复杂度计算问题,加深一下算法功底!

题目一:509. 斐波那契数

题目描述:
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n)

示例 1:
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:
输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:
输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

C++代码

方法一、常规动态规划

class Solution {
public:
    int fib(int n) {
        if(n<2) return n;
        vector<int> dp(n+1);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<n+1;++i){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

方法二、动归空间复杂度优化

class Solution {
public:
    int fib(int n) {
        if(n<2) return n;
        vector<int> dp(2);
        int sum=0;
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<n+1;++i){
            sum=dp[0]+dp[1];
            dp[0]=dp[1];
            dp[1]=sum;
        }
        return dp[1];
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

这一题比较简单主要是题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了,但是还是要清楚地理解每一步,因为一般题目都是要寄几分析推导递推公式和如何初始化!

题目二:70. 爬楼梯

题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

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

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

C++代码

来分析一下,动规五部曲:
定义一个一维数组来记录不同楼层的状态
1.确定dp数组以及下标的含义
dp[i]:爬到第i层楼梯,有dp[i]种方法

2.确定递推公式
如果可以推出dp[i]呢?
从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。
首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!
所以dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性!
3.dp数组如何初始化
题目中说了n是一个正整数,所以直接dp[1] = 1,dp[2] = 2
4.确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
5.举例推导dp数组

举例:当n为5的时候,dp table(dp数组)应该是这样的
在这里插入图片描述
如果代码出问题了,就把dp table 打印出来,看看究竟是不是和自己推导的一样

class Solution {
public:
    int climbStairs(int n) {
        if(n<3) return n;
        vector<int> dp(3);
        int sum=0;
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<n+1;++i){
            sum=dp[1]+dp[2];
            dp[1]=dp[2];
            dp[2]=sum;
        }
        return dp[2];
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

三、通过一道面试题,了解递归算法的时间复杂度计算方法

题目描述:
求x的n次方
方法一、直接迭代方法,1个for循环出结果,时间复杂度为O(n)
方法二、递归初级版

int function2(int x, int n) {
    if (n == 0) {
        return 1; // 0次方是等于1
    }
    return function2(x, n - 1) * x;
}

一般大家都可以想到这个版本,明显递归了n次,乘法操作的时间复杂度一个常数项O(1),所以这个方法的时间复杂度是 n * 1 = O(n).

方法三、递归进阶版

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

我们来分析一下,首先看递归了多少次呢,可以把递归抽象出一颗满二叉树。这个算法,可以用一颗满二叉树来表示(为了方便表示,选择n为偶数16),如图:
图片

递归算法的时间复杂度 当前这颗二叉树就是求x的n次方,n为16的情况,n为16的时候,进行了多少次乘法运算呢?
这棵树上每一个节点就代表着一次递归并进行了一次相乘操作,所以进行了多少次递归的话,就是看这棵树上有多少个节点。
熟悉二叉树话应该知道如何求满二叉树节点数量,这颗满二叉树的节点数量就是2^3 + 2^2 + 2^1 + 2^0 =
15,可以发现:「这其实是等比数列的求和公式,这个结论在二叉树相关的面试题里也经常出现」。
这么如果是求x的n次方,这个递归树有多少个节点呢,如下图所示:(m为深度,从0开始)在这里插入图片描述
「时间复杂度忽略掉常数项-1之后,这个递归算法的时间复杂度依然是O(n)」

方法四、递归终极版

int function4(int x, int n) {
    if (n == 0) {
        return 1;
    }
    int t = function4(x, n / 2);// 这里相对于function3,是把这个递归操作抽取出来
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

看一下递归了多少次,可以看到这里仅仅有一个递归调用,且每次都是n/2 ,所以这里我们一共调用了log以2为底n的对数次。
「每次递归了做都是一次乘法操作,这也是一个常数项的操作,那么这个递归算法的时间复杂度才是真正的O(logn)」。


总结

一、动态规划
英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的

对于动态规划问题,可以拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!

1.确定dp数组(dp table)以及下标的含义
2.确定递推公式
3.dp数组如何初始化
4.确定遍历顺序
5.举例推导dp数组

动态规划debug方法:把dp数组打印出来,看看究竟是不是和上面第5步按照自己思路推导出来的结果一样!

二、递归时间复杂度
「同一道题目,同样使用递归算法,有的人会写出了O(n)的代码,有的人就写出了O(logn)的代码」如果对递归的时间复杂度理解的不够深入的话,就会这样!
所以了解如何计算递归程序的时间复杂度对于优化算法非常有帮助!
递归算法的时间复杂度本质上是要看: 「递归的次数 * 每次递归中的操作次数」


欢迎大家扫码关注本人公众号:编程复盘与思考随笔

(关注后可以免费获得本人在csdn发布的资源源码)

公众号主要记录编程和刷题时的总结复盘笔记和心得!并且分享读书、工作、生活中的一些思考感悟!
在这里插入图片描述

想要组队一起参加阿里天池,kaggle,百度飞浆,科大讯飞等AI相关的比赛的同学可以扫下面的二维码加微信一起讨论学习!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Al资料站与复盘笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值