热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

算法面试中的时间复杂度分析

例子:有一个字符串数组,首先将数组中每一个字符串按照字母序排序,之后再将整个字符串按照字典序排序。整个操作的时间复杂度?答:假设最长的字符串长度是s,数组中有n个字符串。对每个字符串进行排序

例子: 有一个字符串数组,首先将数组中每一个字符串按照字母序排序,之后再将整个字符串按照字典序排序。整个操作的时间复杂度?

答: 假设最长的字符串长度是s,数组中有n个字符串。
对每个字符串进行排序: slogs, 共有n个,所以 nslog(s)
所有的字符串进行排序:O(s*nlog(n)) //对字符串进行排序,每一次比较最多为s

==> O(n * slogs) + O(s * nlogn) = O(sn(logn + logs))

算法复杂度有些情况下是和用例相关的

对数据规模有一个概念 -- 封底估算

用下面的一个程序进行测试:

for(int x = 1; x <= 9; x++){
    int n = pow(10, x);
    
    clock_t startTime = clock();
    int sum = 0;
    for(int i = 0; i "10^" <" : "<<double(endTime - startTime)/CLOCKS_PER_SEC <<" s"<

这是一个O(n)的算法在本机4核i7的机器跑出来结果如下:

10^1 : 0 s
10^2 : 0 s
10^3 : 0 s
10^4 : 0 s
10^5 : 0 s
10^6 : 0 s
10^7 : 0.03125 s
10^8 : 0.25 s
10^9 : 2.4375 s

也就是说,如果程序要求在1s内跑出结果,数据规模最好不要超过10^8,不要达到10^9,也就是说:
O(n^2): 大约可处理10^4级别的数据
O(n^logn): 大约可处理10^7级别数据
O(n): 大约可处理10^8级别数据

常见的复杂度分析

\(O(1)\)

void swap(int &a, int& b){
    int tmp = a; a = b; b = tmp;
}

\(O(n)\) --- 常系数很可能不为1

int sum(int n) {
    int sum = 0;
    for(int i = 1; i <= n; i++) {
        sum += n;
    }
    return sum;
}

\(O(n^{2})\)

// 选择排序
for(int i = 0; i int minIndex = i;
    for(int j = i + 1; j if(arr[j] 

\(O(logn)\)

// lower_bound
int binSearch(const vector<int>& nums, int lo, int hi, int key) {
    while(lo int mid = lo + (hi - lo) / 2;
        if(nums[mid] else{
            hi = mid;
        }
    } 
    return lo;
}

考虑下面这个例子:n经过几次除以10的操作后,等于0? 答案是log_{10}n

// Warnning!! this code is buggy
// 需要考虑各种情况
string intToString (int num) {
    string s = "";
    while(num) {
        s += '0' + num % 10;
        num /= 10
    }
    reverse(s);
    return s;
}

以2为底和以10为底的对数,在数量级上没有区别。(只是一个线性关系)

\(O(nlogn)\)

虽然下面的代码也是两重循环,但是复杂度却是\(O(nlogn)\)的,因为外层循环是指数级增加的(每次乘以2)

void hello(int n) {
    for(int sz = 1; sz for(int i = 1; i "Hello" <

复杂度实验

我明明写的是\(O(nlogn)\)的算法,面试官却说我是\(O(n^{2})\)的?

可以自己进行验证,看能够处理什么级别的数据规模,参考封底估算。
实验,观察趋势。比如每次将数据规模提高两倍,观察时间变化.

递归算法的复杂度分析

对于单次递归调用,复杂度一般为\(O(T*depth)\), 写递推表达式进行推导。

如下面的代码:

double pow(double x, int n) {
    assert(n >= 0);
    if(n == 0) return 1;
    
    double t = pow(x, n/ 2);
    if(n %2) 
        return x*t*t;
    else
        return t*t;
}

递归深度\(depth = logn, T = 1\),因此复杂度为\(O(logn)\)

int f() {
    //递归基 here
    return f(n - 1) + f(n - 1);
}

多次递归调用,递归深度\(depth = n, 每次操作2\),可以推导出算法复杂度为指数级\(O(2^n)\)
\[\begin{equation}\begin{split}\\f(n) &= 2f(n-1)\\&= 4(n-2)\\&= 8f(n-3)\\&\cdots\\&= 2^{n}f(1)\\&= O(2^{n})\\\end{split}\end{equation} \tag{1}\]

简单不严谨地分析快速排序的复杂度:

快速排序的每一次partition操作可构造出这么一个位置,即左边的所有值都比轴点小,右边的都比轴点大,因此\(f(n) = 2*f(n/2) + f(partition)\) 而partition操作只需要做一次循环,所以是一个O(n),所以
\[\begin{split}\\f(n) &= 2f(n/2) + O(n)\\&= 4f(n/4) + O(n) + 2*O(n/2)\\&= 8f(n/8) + O(n) + 2*(n/2) + 4*(n/4)\\&\cdots\\&= 2^{\log_{2}n} * f(1) + \underbrace{ O(n) + O(n) +\cdots+ O(n) }_{k = \log_{2}n个}\\&= n + O(n*\log_{2}n)\\&= O(n * \log_{2}n)\\\end{split} \tag{2}\]
当然,严谨的分析还需要引入概率(随机分布)。这里只是简单不严谨的推导。

均摊复杂度分析 Amoritzed Time

典型例子:动态数组(vector)
每一次动态扩容(resize()),需要开辟一个新的空间,然后进行一一赋值,这样一个操作的复杂度是\(O(n)\)那么问题来了:vector push_back的平均复杂度是多少?

假设当前数组容量为n,从空到满,每一次操作的消耗是\(O(1)​\). 如果此时再来一个元素,就需要resize, 那么最后这一次操作耗费为\(O(n)​\), 那么平均来看,过去n+1次操作的总花费为
\[ \underbrace{ O(1) + O(1) +\cdots+ O(1) }_{n个} + O(n) = O(2n)\]
那么分摊来看每一次push_back的操作花费为\(O(\frac{2n}{n+1}) = O(2) = O(1)\)

那么问题又来了,如果pop_back的时候,发现size为当前capacity的1/2就resize,那么时间复杂度是多少?
假设当前数组容量为2n,从满到一半,每一次操作的消耗是\(O(1)\),如果此时再pop_back,需要再消耗的时间为\(O(2*n)\), 那么这个视角下的均摊分析还是为级别O(1)
可是换一种奇怪的情况:数组满后,resize为两倍,需要O(n);此时又要删除,那么又到达临界点了,此时要resize缩容,又需要\(O(n)\),那么在这种退化情况下,单次操作复杂度退化到了\(O(n)\), 这种情况也被称之为复杂度的振荡

正确的做法是什么呢?pop_back要等到size为capacity的1/4再resize缩容。

面试被问过的复杂度分析

假设现在的动态数组/哈希表有n个元素,vector的初始大小为1,问从开始到现在,共有多少次复制操作?

最后一次复制:n/2参与

倒数第二次复制:n/4参与

倒数第三次复制:n/8参与

...

第二次:2个参与

第一次:1个参与

\[\therefore S(n) = \frac{n}{2} + \frac{n}{4} + \frac{n}{8} +\cdots+4+2+1 \tag{3}\]

\[\Rightarrow 2S(n) = n + \frac{n}{2} + \frac{n}{4} + \frac{n}{8} +\cdots+4+2 \tag{4}\]

很容易就可以得出

\[S(n) = n - 1 \tag{*}\]

也就是说,每一次插入新元素的复杂度能够分摊为\(O(1)\)的级别


推荐阅读
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了最长上升子序列问题的一个变种解法,通过记录拐点的位置,将问题拆分为左右两个LIS问题。详细讲解了算法的实现过程,并给出了相应的代码。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
author-avatar
喵喵妈70929
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有