结合LeeCode的实战题目 Climbling Stairs来学习如何看读程序判定时间和空间复杂度

时间复杂度

七种时间复杂度
O(1): Constant Complexity 常数复杂度
O(log n): Logarithmic Complexity 对数复杂度
O(n): Linear Complexity 线性时间复杂度
O(n^2): N square Complexity 平方
O(n^3): N cubic Complexity 立方
O(2^n): Exponential Growth 指数
O(n!): Factorial 阶乘

注意:只看最高复杂度的运算

注意:前面的常数系数是不用进行考虑的。如果是线性时间复杂度o(n),它可能运算了n次,也可能运算了2n次。

那么我们如何判定时间复杂度呢?最常用的方式就是直接看这个函数,或者看这段程序根据n的不同情况,会运行多少次。举例如下:
在这里插入图片描述
由于第一段程序只会执行一次,所以它的时间复杂度为 O(1);而第二段代码,虽然它会执行3次,但是我们不需要关心它前面的常熟系数,所以它的时间复杂度还是常数级的 O(1)
在这里插入图片描述
上面第一段代码虽然只有3行,println语句虽然也只有1行,但是代码却发生了很大变化,当n=1时,println语句只执行一次;但是n=1000时,它就要执行1000次,所以随着n的不同,这段代码的执行次数也不同,所以它的时间复杂度和n是线性关系。假设n等于多少的话,它的执行次数,它的运行复杂度就是n的一次方,所以是O(n) 线性的时间复杂度。
同理如果是嵌套循环,n=100时,第二段代码中System.out.println() 会执行 100*100=10000次,所以它的时间复杂度就是 O(n^2)。
思考:如果这两个循环并不是嵌套的,而是并列的,那么它的时间复杂度是多少呢?
答案是 O(n) 。因为是并列的,所以就是 2n 次,由于不关心前面的常数系数,所以是 O(n) 的时间复杂度。

接下来看下面这段代码:
在这里插入图片描述
当n=4时,i 只会执行2次;n=8时,i 会执行3次,那么这个函数体执行的次数永远是log2(n),所以它的时间复杂度是 O(log n) 。而下面的 Fibonacci数列 求第n项是用了递归形式,这里就涉及到了怎样计算递归程序的时间复杂度?它答案的是 k的n次方 ,k是一个常数。所以使用递归求Fibonacci是很慢的,指数级的时间复杂度

接下来通过一个图看一下时间复杂度:
时间复杂度曲线图

                       时间复杂度曲线图

当n在5以内时,不同的时间复杂度都差不多;如果n增大,指数级增长的很快,它的时间复杂度是很大的。所以在写代码时,如果能够优化时间复杂度,比如从 2^n 降到 n^2,那么n很大时,它们的差别可谓天壤之别。

所以我们在平时写程序时,要对自己程序的时间和空间复杂度有所了解,能够下意识地分析所写代码的时间和空间复杂度;还有就是最好以最简洁的时间和空间复杂度完成程序。

那么我们就来看一下 不同的程序在写法中完成同样的目标可能会导致时间复杂度的不同,我们来看下这个简单的例题:
例题:从 1+2+…+n 求和
在这里插入图片描述
方法一的时间复杂度为 O(n)
方法二的时间复杂度为 O(1),因为这个语句永远都只执行一次。
由此可见虽然结果相同,但两个方法的时间复杂度却很不同。

判断递归的时间复杂度
关键是要了解递归总共执行了语句多少次?这里我们要借助的是把递归的执行顺序画出一个树型结构,称为递归树
首先我们来看下面这个题目,就是 求Fibonacci数列的第n项:
在这里插入图片描述
通过递归树,我们来看下这个过程:
在这里插入图片描述
每一层,它的节点数就是它的执行次数,是按指数级递增的,所以由此可见到最后一层时,也就是 2^n个节点 ,所以最后总的执行次数一定是指数级的。我们还可以从上图中观察到有重复的节点出现,所以出现了很多冗余的计算,所以它的时间复杂度很复杂,所以以上代码不可取。我们可以加一个缓存,把中间结果缓存起来,或者直接用一个循环来写

主定理
解决所有递归的函数怎么来计算它的时间复杂度?
在这里插入图片描述
第一个是二分查找,一般发生在一个数列本身有序的时候,你要在有序的数列里找到你要的目标数,所以它每次都一分为二,只查一边,所以二分查找的时间复杂度是 log(n)
二叉树的遍历,它为 O(n) 。二叉树的遍历的话,我们会每个节点都访问一次且仅访问一次,所以二叉树的遍历的时间复杂度是 O(n)
第三个情况是在一个排好序的二维矩阵中进行二分查找 ,它的时间复杂度是 O(n) 。如果是一维的数组进行二分查找,就是 logn ,如果是二维的有序矩阵进行查找,就是 O(n)。
最后一个是归并排序,它的时间复杂度是 nlog(n)

分析以下几个面试题

  1. 二叉树遍历 - 前序、中序、后序:时间复杂度是多少?
    O(n) ,这里的 n 代表二叉树里面的树的节点总数,不管是 前序、中序、后序,遍历二叉树时,每个节点会访问一次且仅访问一次,所以他的时间复杂度线性于这个二叉树节点总数,所以它的时间复杂度是 O(n)
  2. 图的遍历:时间复杂度是多少?
    同理可得,图里面的每个节点访问一次且仅访问一次,所以它的时间复杂度是 O(n), n 代表图里面的节点总数。
  3. 搜索算法:DFS(深度优先)、BFS(广度优先)时间复杂度是多少?
    不管是深度优先还是广度优先,因为访问的节点是访问一次,所以它的时间复杂度都是 O(n), n代表搜索空间里面的节点总数
  4. 二分查找:时间复杂度是多少?
    logn

空间复杂度

主要有两条原则:

  1. 数组的长度。代码中数组长度就是你的空间复杂度,如果是一维数组,空间复杂度就是 O(n),如果是二维数组,数组的长度为 n^2,那么空间复杂度就是 O(n^2)
  2. 递归的深度:如果有递归,那么递归最深的深度就是空间复杂度的最大值,如果有有递归又有数组,那么两者之间的最大值就是你的空间复杂度。

接下来我们来看下LeeCode的实战题目 Climbling Stairs(爬楼梯问题)
链接在这:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti- by-leetcode/
在这里插入图片描述
方法一:暴力法
在这里插入图片描述
在这里插入图片描述
空间复杂度:O(n),递归树的深度可以达到 n。

方法二:记忆化递归(加了缓存)
在这里插入图片描述
每次递归时,重复计算过的就保存起来,不需要再次计算。
同时它多了一个数组,数组长度为n
时间复杂度:O(n),树形递归的大小可以达到 n。
空间复杂度:O(n),递归树的深度可以达到 n。

方法三:动态规划
在这里插入图片描述
递归时,不需要存所有的状态,只需要存 i-1 和 i-2 即可,所以进行内存上的优化,不再申请书数组,而是申请两个中间变量即可,再加一个第三方变量就可以不断递推了
时间复杂度:O(n),单循环到 n,需要计算到第 n个斐波那契数。
空间复杂度:O(1),使用常量级空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值