引言:上篇文章我们讲述了时间复杂度,这篇文章我们进一步讲解一下空间复杂度的内容。
空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。 函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
上面是两个很简单的空间复杂度的问题,一般情况下,空间复杂度都是O(N),O(1)
上面两个实例都是和斐波那契数列相关的,但是我们就产生了一个问题,就是实例8它拓展过程中到底申请了多少空间,空间复杂度究竟如何计算,这里我们对这两个斐波那契数列的原理进行一下详细说明:
实例3的图示如上图,可以看到,它是一层一层往下开辟空间的,所以是真真实实地申请了N个大小的空间
上图是实例8 在研究实例8之前,我们插入一小段内容以便加深理解,
如图,a和b所使用的是同一块空间,这里是主函数出发,F1进入,申请了一块空间,F1结束,这块空间是要被销毁的,但是这个时候栈帧销毁是把它还给了系统中而并非彻底销毁,也就是说下一次也就是F2执行的时候,b和a使用的是同一块空间,相当于a和b住酒店,a先住,住完退房给b住,实际上他们是在一个房间住的,房间并没有被销毁,只是还给了房管。
这里是因为在F1里使用到了F2,这个时候只能再开辟一块空间,就好比住房,a还没退房,b就要来住房,那没办法,只能再给b找一间屋子,所以就开辟了两块空间。
明白了上面的道理,我们就来看斐波那契数列, 这里是fibN下放到N-1,然后再到N-2,一直到2,然后递归回去的时候会栈帧销毁,这样全部递归完毕后下一次下放的时候会再次申请同样的空间,所以它开辟出开的空间大小不变,还是N,所以最后空间复杂度是O(N)。
一言以蔽之:时间一去不复返,不可以重复使用;空间用了以后归还,可以重复利用。
下面我们插入一道力扣上面的复杂度OJ练习题:
对这道题,我们在这里提供两个思路:
思路一:
4 3 2 1 5 6 7 ---前n-k个逆置 4 3 2 1 7 6 5 ---后k个逆置 5 6 7 1 2 3 4 ---整体逆置
void reserve(int *nums,int begin,int end)
{
while(begin<end)
{
int tmp=nums[begin];
nums[begin]=nums[end];
nums[end]=tmp;
++begin;
--end;
}
}
void rotate(int* nums, int numsSize, int k){
k%=numsSize;
reserve(nums,0,numsSize-k-1);
reserve(nums,numsSize-k,numsSize-1);
reserve(nums,0,numsSize-1);
}
时间复杂度O(N),空间复杂度O(1)
思路二:
使用memcpy函数
void rotate(int* nums, int numsSize, int k){
k%=numsSize;
int* tmp=(int*)malloc(sizeof(int)*numsSize);
memcpy(tmp,nums+numsSize-k,sizeof(int)*k);
memcpy(tmp+k,nums,sizeof(int)*(numsSize-k));
memcpy(nums,tmp,sizeof(int)*numsSize);
free(tmp);
tmp=NULL;
}
memcpy函数第一个参数是要拷贝到的位置,第二个参数是开始拷贝的位置,第三个参数是拷贝的位数
时间复杂度O(N),空间复杂度O(N)