前端面试(一):算法复杂度分析

前言

啊~~~痛苦的暑期实习面试开始了。

笔者在面试写算法题时就经常被面试官问到:"这个方法的时间复杂度是多少?空间复杂度是多少?"

但笔者只对算法复杂度略知一二,回答的时候都犹犹豫豫不敢确定:"emm,是O(n)吗?"

所以为了避免以上情景再次发生,笔者决定要好好整理一下算法复杂度分析,并以此作为我暑期实习和秋招复习第一个专题,给自己打气也分享给一起找工作的朋友们!

算法复杂度

为什么需要算法复杂度

首先,有时候用算法解决问题时,我们不单单需要考虑问题能否解决,还需要考虑解决问题的性能开销,比如要花多长时间,要消耗多少空间。算法的性能开销需要一个衡量标准,所以我们很需要算法复杂度

其次,算法复杂度会不会计算也是衡量程序员的一个重要标准,算法复杂度的计算可以帮助程序员更好地理解算法的运行,优化程序的性能,并因地制宜地选择算法。

算法复杂度分析的是什么

算法复杂度主要用来分析算法的时间开销和空间开销,我们需要了解时间复杂度空间复杂度

  • 时间复杂度:衡量运行算法完需要的时间。
  • 空间复杂度:衡量运行算法需要的空间。

大O计法

大O计法常常用来描述算法复杂度,它既可以用来描述时间复杂度也可以描述空间复杂度。

以时间复杂度 T(n) = O(f(n)) 为例子,这里 T(n) 表示算法执行的总时间,n 表示数据的规模,f(n) 表示每行代码的执行总次数,O 表示执行时间与 f(n) 的表达式成正比。

O的具体讲解可以看这里啦:# 一文搞懂算法复杂度分析:大O符号你都搞不懂,所以只能搬砖到秃顶?

时间复杂度

算法时间复杂度是通过计算算法所需要运行的时间实现的,算法的时间复杂度计作:T(n) = O(f(n)) , n 为数据的规模,f(n) 表示每行代码的执行总次数,O 表示执行时间与 f(n) 的表达式成正比。

用大O计法来表示的时间复杂度并不代表算法真正运行的时间,而是表示代码运行时间会随数据规模的变化而变化,大O计法也称为渐进时间复杂度。

PS: 笔者感觉用大O计法的时候,只需要关注关键细节,比如循环啊,对于常数次的运算可以忽略哈。

常见的时间复杂度

O(1) 常数级

在下面的代码中,T(n) = O(1),a和b无论怎么变,代码的执行次数还是这么多,运算时间也很稳定。

对于常数级的时间复杂度来说,代码的执行时间不会随着数据规模的增加而增加。

js 复制代码
const sum = (a, b) => {
  return a + b; // t
}

O(n) 线性阶

在下面的这段代码中,T(n) = O(2n+1), 当数据规模n无限增大的时候,低阶、常量都可以忽略不计,T(n) 可以简化表示为 O(n),代码的执行时间会随着数据规模的增加而增加。

js 复制代码
const sum = (n) => {
 let count = 0 ; //t 一个时间单位
 for(let i = 0; i < n; i++){ // t*n
   count = count + i; // t*n  
 }
 return count;
}  

O(m+n) 线性阶

在下面的这段代码中,T(n) = O(m+n), 因为算法运行的时间与两个变量 m、n 相关。

js 复制代码
const sum = (m, n) => {
 let sum1 = 0 ; //t 一个时间单位
 for(let i = 0; i < m; i++){ // t*m
   sum1 = sum1 + i; // t*m
 }

 let sum2 = 0 ; //t 一个时间单位
 for(let i = 0; i < n; i++){ // t*n
   sum2 = sum2 + i; // t*n  
 }
 
 return sum1 + sum2;
}  

O(m*n)

在下面的这段代码中,我们可以看到循环中的关键代码要运行mn次,故 T(n) = O(mn)。

比较特殊的情况是,当 m = n 时, T(n) = O(n*n),代码执行时间随着数据规模的增加而平方增长。

js 复制代码
const sum = (m, n) => {
 let count = 0 ; //t 一个时间单位
 for(let i = 0; i < m; i++){ // t*m
   for(let j = 0; j < n; j++){ // t*m*n
     count = count + i*j; // t*m*n 
   }
 }
 return count;
}  

O(n*n)

在下方的这段代码中,代码执行时间随着数据规模n的增加而平方增长,时间复杂度 T(n) = O(n*n)。

js 复制代码
const sum = (n) => {
 let count = 0 ; //t 一个时间单位
 for(let i = 0; i < n; i++){ // t*n
   for(let j = 0; j < n; j++){ // t*n*n
     count = count + i*j; // t*n*n 
   }
 }
 return count;
}  

O(logn) 对数阶

在下方的这段代码中,代码执行时间随着数据规模n的增加而对数增长,所以时间复杂度 T(n) = O(logn)

js 复制代码
const sum = (n) => {
  let count = 0;
  let i = 1;
  while (i < n) {
    i=i*2;
    count = count + i;
  }
  return count;
}

参考文档中的 O(logn) 推导过程:

在代码中,while循环中每次将i乘以2,直到i大于n,结束此循环。

当循环x次后,i>n,循环结束,也就是2的x次方等于n,即x=log2n,也就是说循环log2n次以后,循环就结束了。代码的时间复杂度为O(logn)。

找出while循环中的结束条件,判断时间复杂度。

带有while循环的代码,大概率是对数时间复杂度为O(logn)

时间复杂度比较

各个算法时间复杂度的大小比较大约是酱紫的:

O(1) <<< O(logn) <<< O(n) <<< O(nlogn) <<< O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2) <<< O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 3 n^3 </math>n3) <<< O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 n 2^n </math>2n)<<< O(n!)

空间复杂度

算法空间复杂度是通过计算算法所需要存储空间实现的,算法的空间复杂度计作:S(n) = O(f(n)) , n 为数据的规模,f(n) 表示 n 所占的存储空间的函数,O 表示存储空间与 f(n) 的表达式成正比。

用大O计法来表示的空间复杂度并不代表算法真正占用的存储空间,而是表示算法占用的存储空间与数据规模的增长关系,大O计法也称为渐进空间复杂度。

常见的空间复杂度

O(1) 常数级

在下面的代码中,算法只占用了容量为1的数组,S(n) = O(1)。

js 复制代码
const array = () => {
  let arr = new Array(1).fill(0);
  return arr;
}

O(n) 线性阶

在下面的这段代码中,算法开辟了容量为n的数组,算法占用的存储空间会随着数据规模n的增加而增加,所以S(n) = O(n)。

js 复制代码
const array = () => {
  let arr = new Array(n).fill(0);
  return arr;
}

O(n*n)

在下面的这段代码中,算法创建了一个nn的二维数组,算法占用的存储空间会随着数据规模n的增加而平方增加S(n) = O(nn)。

js 复制代码
const matrix = (n) => {
 let m = new Array(n).fill(0).map(item => new Array(n).fill(0));
 return m;
}  

空间复杂度比较

一般我们会用到的空间复杂度有 O(1), O(n), O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2), 它们占用的空间大小关系为:

O(1) <<< O(n)<<< O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2)

参考文档

  1. 算法复杂度怎么计算
  2. 干货|算法复杂度分析看这一篇就够了
  3. 数据结构《二》算法时间复杂度 --- 大O记法
  4. 一文讲透算法中的时间复杂度和空间复杂度计算方式
  5. 一文搞懂算法复杂度
相关推荐
Serene_Dream9 小时前
JVM 并发 GC - 三色标记
jvm·面试
愚者游世11 小时前
Delegating Constructor(委托构造函数)各版本异同
开发语言·c++·程序人生·面试·改行学it
信码由缰13 小时前
Spring Boot 面试问题
spring boot·后端·面试
马猴烧酒.1 天前
【面试八股|Java集合】Java集合常考面试题详解
java·开发语言·python·面试·八股
闻哥1 天前
从测试坏味道到优雅实践:打造高质量单元测试
java·面试·单元测试·log4j·springboot
南风知我意9571 天前
【前端面试5】手写Function原型方法
前端·面试·职场和发展
java1234_小锋1 天前
Java高频面试题:SpringBoot如何自定义Starter?
java·spring boot·面试
努力学算法的蒟蒻1 天前
day77(2.5)——leetcode面试经典150
面试·职场和发展
Remember_9931 天前
MySQL 索引详解:从原理到实战优化
java·数据库·mysql·spring·http·adb·面试
❀͜͡傀儡师1 天前
基于大语言模型的简历分析和模拟面试系统
人工智能·语言模型·面试