赞
踩
目录
我们在刷LeetCode上的题目时有时候会碰到有关时间复杂度和空间复杂度的概念,比如类似这种题:
这种题目提出了对时间复杂度和空间复杂度的要求,那么他两究竟什么是意思呢?下面我来为你解答。
在计算机的发展过程中,由于早期条件的限制以及技术的不成熟,技术人员往往将更多的注意力放在空间上,因为内存大小有限,所以写的代码必须有严格的空间占用限制。空间复杂度的概念也由此而来。
随着技术的发展,内存越来越大,CPU速度也越来越快,对空间复杂度的重视逐渐下降,转而集中到程序执行时间速度上去了。时间复杂度就是程序算法的执行次数。注意:时间复杂度不能简单理解为程序在计算机上的运行时间,因为在不同计算机上同一条指令执行的时间可能是不同的,所以用次数来表示时间复杂度。
空间复杂度相对简单,并且随着技术的发展,对时间复杂度的重视超过了空间,但很多地方还是对空间复杂度有所要求的,尤其是刷题时经常会碰到这种限制。
空间复杂度怎么计算呢?有一种专门的计算方法叫大O渐进法:
大O渐进法实际上是一种估算,它不取精确的表达式和计算结果,而是取大概的结果。
如常数就直接算作1, 表达式只保留最高阶数,并且去除系数。(如:2N*(N-1)用N^2来表示)
空间复杂度计算的是算法里边变量使用个数,一般来说就只有O(1)和O(N),相对简单。
注意:一定是算法里面使用变量的个数,和给的数组、变量没有关系
在冒泡排序里,空间复杂度是O(1),为什么不是O(N)?别看这里给了数组,元素是N个,但是我们关注的是算法里面使用变量的个数,这里面创建了end,i,exchange,也就是常数个变量,所以空间复杂度是O(1)。像这里的i,其实只创建了一次,每一次循环用的其实都是同一块空间,编译器没有开辟新的空间。
注意:这里求的是0~n的斐波那契数组,开辟的是n个空间,所以空间复杂度是O(N)。
像O(N),它是线性的,空间复杂度随着N变化而线性变化,而O(1)是非线性的,空间复杂度不随N变化而变化。
阶乘递归里面空间复杂度是O(N),试想一下,递归一次创建一块空间,层层递归,有多少层就创建了多少空间。
比如说递归一层创建一块空间是3,N层递归就是3N,还要返回上去,那就是6N,系数可以忽略,空间复杂度就是O(N)。
所以,递归的空间复杂度就是递归深度。
时间复杂度计算的计算方法,也同样有大O渐进法
来看几个例子来帮助理解:
再解释一下:像上面的实例一、二,O(N) , 程序算法的执行次数是随着N改变而改变的,是呈线性变化的,而O(1)不是线性变化的,是固定的。
我用编译器编个代码再给大家看一下:
可以看到,这里循环次数设定为一千万次时,执行程序也就花了9毫秒,改为一亿次后也仅仅98毫秒,非常之快而且相差微乎其微,这也就是为什么说常数可以用1来代替。
这里的时间复杂度要怎么计算呢?
其实这里查找字符需要分情况(最好、最坏、平均)
1、最好情况:第一次查找就找到了O(1)
2、最坏情况:直到最后一次查找才找到,或者没找到 O(N)
3、平均情况:在差不多中间位置找到了,O(N/2)
我们时间复杂度体现的是最坏情况,所以是O(N)
这是二分查找的一个大概流程,如果要查找的数比中间值大,那么说明数在mid的右边,下次从mid+1的位置开始查找。如果要查找的数比中间值小,那么说明数在mid的左边,下次从mid-1的位置开始查找。
知道了二分查找的原理,那么时间复杂度如何计算呢?
试想一下,一次查找就去了一半的数字,每次去一半,也就是说:2*2*2*.....2=N
运用高中数学对数的知识,时间复杂度可表示为:O(logN) ,也就是以2为底N的对数。
因为时间复杂度里面对数基本都是以2为底的,像这里的二分查找、二叉树....都是2为底的对数,所以规定以2为底的对数写成logN;
二分查找的效率确实是非常高的,从时间复杂度O(logN)就能看出来。
如果N取2^10,O(N)自然就是2^10,也就是 遍历 算法执行1024次,而二分查找算法只执行10次。把数字放大一点,如果N取2^30,遍历 算法执行的次数非常非常多,而二分查找只要30次。所以说二分查找的效率很高。
但二分查找的前提是要是个有序数组,所以对无序数组使用二分查找之前要先排序。
而快速排序的时间复杂度是O(N*logN),因此整个的时间复杂度是O(N*logN+logN) 。
递归的时间复杂度计算法:递归次数*每次递归函数中次数
像这里,每次递归函数中次数为O(1),因为这里递归函数里面只执行了一个三目操作符,也就是只执行了一次,虽然有个*N,但可以视为常数次,也就是O(1)。
递归次数可以画图理解:
递归下去是N次,回上来也是N次,一共是2*N次,系数也可省略,所以时间复杂度是:O(N)。
运用上面递归计算的公式,每次递归函数中次数还是一样,为O(1)。
递归次数也依然画图理解一下:
可以看到,第一层是2^0 , 第二层是2^1 ......一次递归下去(返回不看了)
这样执行次数就是2^0+2^1+....+2^(N-1)-(常数),根据等比数列求和公式得出
O(2^N-1-(常数)),这里减的常数是上图里类二叉树右边缺少的式子 ( 如:Fib(N-2)分不出Fib(N-1) )。
一、消失的数字
力扣https://leetcode.cn/problems/missing-number-lcci/
这是一道面试题,如果不加在O(n)时间内完成这一限制,有很多解法,现在加了时间复杂度的条件,我们来分析一下思路:
思路1、0~n的数,缺了一个,要找出来,可以用冒泡排序的思想,两个相邻的数两两相比,后一个比前一个大1,如果不是,那说明这个位置缺了一个数,就找到了。
思路没问题,看时间复杂度符合条件吗?很遗憾,冒泡排序O(N^2),不符合。
思路2、将0~n的数加起来,减去这个缺一个数的数组,差为多少,就是缺的那个数。0~n的数相加(常数运算),时间复杂度O(1), 后面数组相加,可以用循环表示,所以O(N),符合条件。
代码如下:
思路 3、“异或^” 我们知道:a^0=a; a^a=0,两个相同的数异或就为0且不考虑顺序(如:a^b^c^a^b=c)
所以这里只需要用0~n的数异或上数组中每一个数,就可以得到缺的那个数。时间复杂度是O(N),因为这里依次异或用的是一个循环。
代码如下:
- int missingNumber(int* nums, int numsSize){
- int x=0;
- for(int i=0;i<numsSize+1;++i)
- {
- x^=i;
- if(i<numsSize)
- {
- x^=*(nums+i);
- }
- }
- return x;
- }
二、只出现一次的数字力扣https://leetcode.cn/problems/single-number/
和上一题思路三类似,可以使用异或去解。数组里只有一个元素只出现一次,其余都出现了两次,将他们依次异或就得到了单独的那个元素。
代码如下:
- int singleNumber(int* nums, int numsSize){
- int x=0;
- for(int i=0;i<numsSize;++i)
- {
- x^=*(nums+i);
- }
- return x;
- }
三、只出现一次的数字力扣https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
这个题目就有难度了,首先两个数字不太好找,而且时间复杂度和空间复杂度都有限制。
还是从异或的角度思考解决方法。
还是和上面两题一样,先设一个ret=0,将数组里的数依次与它异或,出现两次的数就没了,只剩下出现一次的两个数x,y ,所以,ret=x^y;
到了这里,要想分别得出x,y有难度,我们找不出其中的规律。
从其他角度思考:首先,ret不为0,因为x !=y。 那么x^y就必定有1的位,那么x,y就必定有位数是不同的,即一个为0,一个为1。
假设ret第n位为1,那么x,y第n位一个为0,一个为1。把原数组中的数按照第n位划分,为0的分1组,为1的分另一组。例如下图:
我们可以得出:出现两次的数不一定进哪一组(也不关心),出现一次的数(3,5)一定进不同的组,于是,我们在两组分别再异或,就可以得出x,y了。
思路有了,下面写代码:
刚接触LeetCode的小伙伴可能不清楚这样的怎么写,怎么返回值。
在这里提示了要返回一个数组arry,并且这个数组是要被malloc的,也就是说将这两个数放到数组arry里面返回,注意返回arry只是返回数组首元素,还得返回数组长度2,所以题中给了*returnSize,这是输出型参数,不是给你使用的,是从函数外部传进来的长度,让你解引用改变它的值,返回数组长度用的。
代码如下:
- int* singleNumbers(int* nums, int numsSize, int* returnSize){
- int *arry=(int*)malloc(sizeof(int)*2);
- *returnSize =2;
- int x=0,y=0;
- int ret=0;
- for(int i=0;i<numsSize;++i){
- ret^=*(nums+i);
- }
- int j=0;
- for(;j<32;++j){
- if( (ret>>j)&1)
- break;
- }
- for(int k=0;k<numsSize;++k){
- if( (*(nums+k)>>j)&1)
- {
- x^=*(nums+k);
- }
- else
- {
- y^=*(nums+k);
- }
- }
- arry[0]=x;
- arry[1]=y;
- return arry;
- }
关于时间复杂度和空间复杂度的讲解就这么多了,感谢大家!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。