Leetcode Lect3 时间复杂度/空间复杂度
时间复杂度
| 复杂度 | 可能对应的算法 | 备注 |
|---|---|---|
| O(1) | 位运算 | 常数级复杂度,一般面试中不会有 |
| O(logn) | 二分法,倍增法,快速幂算法,辗转相除法 | |
| O(n) | 枚举法,双指针算法,单调栈算法,KMP算法,Rabin Karp,Manacher's Algorithm | 又称作线性时间复杂度 |
| O(nlogn) | 快速排序,归并排序,堆排序 | |
| O(n^2) | 枚举法,动态规划,Dijkstra | |
| O(n^3) | 枚举法,动态规划,Floyd | |
| O(2^n) | 与组合有关的搜索问题 | |
| O(n!) | 与排列有关的搜索问题 |
在面试中,经常会涉及到时间复杂度的计算。当你在对于一个问题给出一种解法之后,面试官常会进一步询问,是否有更优的方法。此时就是在问你是否有时间复杂度更小的方法(有的时候也要考虑空间复杂度更小的方法),这个时候需要你对常用的数据结构操作和算法的时间复杂度有清晰的认识,从而分析出可优化的部分,给出更优的算法。
例如,给定一个已经排序的数组,现在有多次询问,每次询问一个数字是否在这个数组中,返回True or False.
方法1: 每次扫描一遍数组,查看是否存在。
这个方法,每次查询的时间复杂度是: O(n)方法2:由于已经有序,可以使用二分查找的方法。
这个方法,每次查询的时间复杂度是: O(logn)方法3:将数组中的数存入Hashset。
这个方法,每次查询的时间复杂度是: O(1)
可以看到,上述的三种方法是递进的,时间复杂度越来越小。
在面试中还有很多常见常用的方法,他们的时间复杂度并不是固定的,都需要掌握其时间复杂度的分析,要能够根据算法过程自己推算出时间复杂度。
T 函数推导法
我们介绍一种时间复杂度的推导方法:T函数推导法
比如二分法。二分法是每次通过 O(1) 的时间将规模为 n 的问题降低为规模为 n/2 的问题。
这里我们用 T(n) 来表示规模为 n 的问题在该算法下的时间复杂度,那么我们得出推导公式:
T(n)=T(n/2)+O(1)
我们来逐个说明一下这个公式的意义。
首先 T 代表的是 Time Complexity, nnn 代表的是问题规模(二分法里就是数组的大小)。
那么 T(n) 代表的就是:求处理问题规模为n的数据的时间复杂度是多少。注意这里是一个问句,不是一个答案。
T(n) 根据算法的不同可以是 O(n) 也可以是 O(nlogn) 或任何值,而 O(n) 就是 O(n)。
然后 O 代表的是时间复杂度。O(1) 就意味着,你大概用一个 if 语句,或者简单的加加减减,就可以完成。O 在这里的意思是数量级约等于。在 O 的世界里,我们只考虑最高项是什么,不考虑系数和常数项。比如:
O(100n)=O(n)
O(n^2+n)=O(n^2)
O(2^n+n^2+10)=O(2^n)
如何推导 T 函数
我们可以使用不断展开的方法进行推导:
T(n) = T(n/2) + O(1)
= T(n/4) + O(1) + O(1)
= T(n/8) + O(1) * 3
= T(n/16) + O(1) * 4
...
= T(1) + O(1) * logn
= O(logn)
在时间复杂度的领域里,有如下的一些性质:
T(1)=O(1) // 解决规模为1的问题通常时间复杂度为O(1)。这个不100%对,但是99.9%的情况下都是如此。
k∗O(n)=O(kn)
O(n)+O(m)=O(n+m)
上面的方法,是采用 T 函数展开的方法,将二分法的时间复杂度最终用 O(...) 来表示。
空间复杂度
类似于时间复杂度,空间复杂度就是衡量算法运行时所占用的临时存储空间的度量。也是一个与问题规模n有关的函数。
算法所占用的空间主要有三个方面:算法代码本身占用的空间、输入输出数据占用的空间、算法运行时临时占用的空间。
其中,代码本身和输入输出数据占用的空间不是算法空间复杂度考虑的范围内,空间复杂度只考虑运行时临时占用的空间,又称为算法的额外空间(Extra space)。
临时占用的空间包括:
- 为参数列表中形参变量分配的空间
- 为函数体中局部变量分配的空间
(如果是递归函数,需要将上述两部分占用空间的和乘以递归的深度,这是堆栈空间)
在递归函数中,除了变量和数组所开辟的临时空间以外,还有一个空间我们需要纳入考虑,就是递归时占用的栈空间(Stack)。
递归函数需要保存当前的环境,以便在递归返回的时候能够还原之前的现场。因此递归的深度越深,所要占用的栈空间越大。当空间超出一定范围的时候就会出现程序爆栈(Stack Overflow)的情况。
很多博客文章中会写堆栈空间与递归调用的次数成正比,这个是不完全正确的,应该是与递归的深度成正比(此处只讨论单线程)。
因为递归在返回到上一层的时侯,就会将本层的空间释放,因此占用的栈空间不会比最深的一次调用所占用的空间更多。
在面试中,很多时候面试官给出的问题会附带一个“不能使用多余的空间”这样的要求。很多时候这是在要求你的空间复杂度只能是O(1)的,也就是你只能开几个辅助变量,而不能开大数组。
其他常见的还有,分析一下你的算法空间复杂度,寻找空间复杂度更优的解法等。
一般来说,算法占用的时间和空间会是两个互相平衡的元素,有的时候我们牺牲空间来换取时间,有的时候我们牺牲时间来换取空间。
在面试中常见算法的空间复杂度:
快速排序: 最优:O(logn),最差:O(n)
二分查找: O(1)
最短路(Dijkstra)算法: O(V) (V表示点集大小)
大部分的空间复杂度计算方法
累加下面两个部分的内容即是你代码的空间复杂度:
- 你的代码里开辟了多少新的空间(new 了多少新的内容出来)
- 你的递归深度 * 递归函数内部的参数和局部变量所占用的空间
以快速排序为例子
快速排序的思路如下:
1.选择一个基准元素,将原数组分为两部分,左边部分小于该元素,右边部分大于该元素。
2.分别递归处理左边和右边。
最好情况:
每次都能恰好将数组分成左右相同长度的两部分,需要的递归深度是:lgnlgnlgn,每次将数组分成两部分时,我们选择不使用辅助数组,在原数组上“就地”处理,所以每层的空间是O(1)
因此总的复杂度是:O(logn)最差情况:
每次都将数组分成长度差最大的两部分,即一边只有一个元素,其余的在另外一边,最大深度为:n, 因此空间复杂度为:O(n)
有些递归算法的空间复杂度是稳定的,不会退化,快排的递归深度与其每次选择的“基准值”有很大关系,因此存在退化的情况。
内存中的栈空间与堆空间
分解质因数
以 sqrt(n)为时间复杂度的算法并不多见,最具代表性的就是分解质因数了。
题目描述
http://www.lintcode.com/problem/prime-factorization/
具体步骤
- 记up=sqrt(n)作为质因数k的上界, 初始化k=2
- 当k<=up 且 n不为1 时,执行步骤3,否则执行步骤4。
- 当n被k整除时,不断整除并覆盖n,同时结果中记录k,直到n不能整出k为止。之后k自增,执行步骤2。
- 当n不为1时,把n也加入结果当中,算法结束。
几点解释
- 不需要判定k是否为质数,如果k不为质数,且能整出n时,n早被k的因数所除。故能整除n的k必是质数。
- 为何引入up?为了优化性能。当k大于up时,k已不可能整除n,除非k是n自身。也即为何步骤4判断n是否为1,n不为1时必是比up大的质数。
- 步骤2中,也判定n是否为1,这也是为了性能,当n已为1时,可早停。
代码
1 public List<Integer> primeFactorization(int n) {
2 List<Integer> result = new ArrayList<>();
3 int up = (int) Math.sqrt(n);
4
5 for (int k = 2; k <= up && n > 1; ++k) {
6 while (n % k == 0) {
7 n /= k;
8 result.add(k);
9 }
10 }
11
12 if (n > 1) {
13 result.add(n);
14 }
15
16 return result;
17 }
复杂度分析
最坏时间复杂度 O(sqrt{n})。当n为质数时,取到其最坏时间复杂度。
空间复杂度 O(log(n)),当n质因数很多时,需要空间大,但总不会多于O(log(n))
Leetcode Lect3 时间复杂度/空间复杂度的更多相关文章
- 时间复杂度&空间复杂度
时间复杂度 参考链接: http://univasity.iteye.com/blog/1164707 空间复杂度 http://blog.csdn.net/booirror/article/deta ...
- 数据结构中常用的排序算法 && 时间复杂度 && 空间复杂度
第一部分:数据结构中常用的排序算法 数据结构中的排序算法一般包括冒泡排序.选择排序.插入排序.归并排序和 快速排序, 当然还有很多其他的排序方式,这里主要介绍这五种排序方式. 排序是数据结构中的主要内 ...
- 计算时间复杂度&空间复杂度
1.下面函数的复杂度是: long foo(long x){ if(x<2) return 1; return x*x*foo(x-1); } 解析: 当n>=2时 foo(n)=n^2* ...
- Leetcode Lect3 内存中的栈空间与堆空间
内存中的栈空间与堆空间 我们通常所说的内存空间,包含了两个部分:栈空间(Stack space)和堆空间(Heap space) 当一个程序在执行的时候,操作系统为了让进程可以使用一些固定的不被其他进 ...
- Leetcode Lect3 二分法总结
二分法模板 非递归版本: public class Solution { /** * @param A an integer array sorted in ascending order * @pa ...
- [Java初探外篇]__关于时间复杂度与空间复杂度
前言 我们在前面的排序算法的学习中了解到了,排序算法的分类,效率的比较所使用到的判断标准,就包括时间复杂度和空间复杂度,当时因为这两个定义还是比较难以理解的,所以决定单独开一篇文章,记录一下学习的过程 ...
- 数据结构&算法(二)_算法基础之前传(递归、时间复杂度、空间复杂度、二分查找)
什么是算法: 间而言之算法(Algorithm):一个计算过程,解决问题的方法 递归的两个特点: 调用自身 结束条件 递归示例: def func(x): : print("我的小鲤鱼&qu ...
- 【LeetCode题解】排序
1. 排序 排序(sort)是一种常见的算法,把数据根据特定的顺序进行排列.经典的排序算法如下: 冒泡排序(bubble sort) 插入排序(insertion sort) 选择排序(selecti ...
- LeetCode每天一题之两数之和
这个LeetCode刷题系列的博客权当是为自己记一下笔记吧.博客系列会从LeetCode的第一题开始刷,同时会从零开始学习[因为我就是零/(ㄒoㄒ)/~~].同时,如果有写错的地方,希望大佬们在评论区 ...
随机推荐
- Sql 统计一个表有多少列
SELECT COUNT(syscolumns.name) FROM syscolumns , sysobjects WHERE syscolumns.id = sysobjects.id AND s ...
- 洛谷 P2783 有机化学之神偶尔会做作弊(Tarjan,LCA)
题目背景 LS中学化学竞赛组教练是一个酷爱炉石的人. 有一天他一边搓炉石一边监考,而你作为一个信息竞赛的大神也来凑热闹. 然而你的化竞基友却向你求助了. “第1354题怎么做”<--手语 他问道 ...
- postgresql相关sql集锦
1.类似于oracle的listagg->string_agg SELECT area_county,)total FROM project_info GROUP BY area_county ...
- 前端每日实战:86# 视频演示如何用纯 CSS 创作一个方块旋转动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/gjgyWm 可交互视频 此视频是可 ...
- Zabbix通过邮件发送Screen图形报表实现
在使用Zabbix的过程中,我们通常会建立一些需要的Screen图形报表来汇总需要监控的Graph. 而下面的两个脚本,则是通过从Zabbix数据库中获取所有的Screen图形参数,提供Zabbix的 ...
- yield与gen.coroutine
def d(): for i in range(2): yield i def b(): yield d() print("b") yield "bb" def ...
- man lsof
LSOF(8) LSOF(8) NAME lsof - lis ...
- 利用xcode Build生成模拟器运行包
真机只能运行.ipa包 模拟器上只能运行.app包 xcode中生成.app包步骤: 启动xcode IDE,打开gigold源码工程 [project]——[gigold]——[Basic]:修改V ...
- ES6基本用法
es6是JS(JavaScript)的下一个版本. 新增了let命令,用来声明变量.变量在第一个花括号内有用,先声明后引用.不允许重复声明.存在暂时性死区. const声明一个只读的常量.一旦声明,常 ...
- centos6.5安装nginx1.16.0
参考: centos7 编译安装nginx1.16.0( 完整版 ) https://blog.csdn.net/weixin_37773766/article/details/80290939 ...