空间复杂度

leetcode 26 – 删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

if(nums.length < 1) return 0;
    	  int i = 0;
    	  for(int j=1; j<nums.length; j++) {
    		  if(!(nums[i] == nums[j])) {
    			  nums[i+1] = nums[j];
    			  i++;
    		  }
    	  }
 return i+1;

复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。

空间复杂度: 全称是 渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
时间复杂度: 全称是 渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。

1、空间复杂度

一个程序的空间复杂度是指运行完一个程序所需内存的大小。
利用程序的空间复杂度,可以对程序的运行所需的内存有个预先估计。

程序执行时所需存储空间包括以下两部分:

  • 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。

  • 可变空间。这部分空间主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。

我们在写代码时,完全可以用空间来换取时间,比如字典树、哈希等都是这个原理。HashMap.get()、put()都是O(1)的时间复杂度。

空间复杂度为O(1):有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是“就地”执行的,是节省存储的算法,空间复杂度为O(1)。

空间复杂度为O(n):有的算法需要占用的临时工作单元与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况。

常见的空间复杂度就是O(1)、O(n)、O(n^2),像是O(logn)、O(nlogn)对数阶的复杂度平时都用不到,而且空间复杂度分析比时间复杂度分析要简单很多。

(1)空间复杂度为:O(1)

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

代码中的i、j、m所分配的空间都不随着处理数据量变化,因此它的空间复杂度S(n) = O(1)

(2)空间复杂度:O(n)

void print(int n) {
  int i = 0;
  int[] a = new int[n];
  for (i; i <n; ++i) {
    a[i] = i * i;
  }
}

第三行new了一个数组,这个数组占用的大小为n,虽然后面有循环,但是没有再分配新的空间,因此空间复杂度S(n) = O(n)。

2、时间复杂度

(1)T(n) = O(2n+2) (看不懂没关系,后面有解释) 时间复杂度:O(n)

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

假设每行代码的执行时间都是一样的,为unit_time单位时间。

第 2、3 行代码分别需要== 1 个 unit_time== 的执行时间,第 4、5 行都运行了 n 遍,所以需要 2n*unit_time 的执行时间,所以这段代码总的执行时间就是== (2n+2)*unit_time==。可以看出来,所有代码的执行时间 T(n) 与每行代码的执行次数成正比。

(2)T(n) = O(2n2+ 2n + 3) 时间复杂度:O(n^2)

int cal(int n) {
   int sum = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1;
     for (; j <= n; ++j) {
       sum = sum +  i * j;
     }
   }
 }

第 2、3、4 行代码,每行都需要 1 个 unit_time 的执行时间,第 5、6 行代码循环执行了 n 遍,需要 2n * unit_time 的执行时间,第 7、8 行代码循环执行了 n2遍,所以需要 2n2 * unit_time 的执行时间。所以,整段代码总的执行时间 T(n) = (2n2+ 2n + 3)*unit_time。

规律:所有代码的执行时间T(n)与每行代码的执行次数成正比
把规律总结成一个公式: T(n) = O(f(n))
其中,f(n)表示每行代码执行的次数总和,因为是一个公式,所以用f(n)来表示; O表示代码的执行时间T(n)与f(n)表达式成正比。

上面是大O时间复杂度的由来和表示方法,但是分析代码的时间复杂度,一般有三种方式:(1)只关注循环执行次数最多的一段代码;(2)总的时间复杂度就等于量级最大的那段代码的时间复杂度;(3)嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。

几种常见的时间复杂度实例
1、常量阶 O(1)O(1)O(1)
2、对数阶 O(logn)O(logn)O(logn)
3、线性阶 O(n)O(n)O(n)
4、线性对数阶 O(nlogn)O(nlogn)O(nlogn)
5、平方阶 O(n2)、立方阶 O(n3)……k 次方阶 O(nk)
6、指数阶 O(2n)
7、阶乘阶 O(n!)

(1)O(1)

int a = 1;
int b = 2;
int c = 3;

一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
(2)O(logn)、O(nlogn)

 i=1;
 while (i <= n)  {
   i = i * 2;
 }

执行次数为log2n,所以,这段代码的时间复杂度为O(log2n)

 i=1;
 while (i <= n)  {
   i = i * 3;
 }

执行次数为log3n,所以,这段代码的时间复杂度为O(log3n)
实际上,不管是以2为底、以3为底还是以10为底,我们可以把所有对数阶的时间复杂度都记为O(logn),忽略对数的底。

for(m = 1; m < n; m++) {
    i = 1;
    while(i < n) {
        i = i * 2;
    }
}

上述的时间复杂度为O(nlogn)

(3)O(m+n)、O(m*n)
代码的复杂度由两个数据的规模来决定

int cal(int m, int n) {
  int sum_1 = 0;
  int i = 1;
  for (; i < m; ++i) {
    sum_1 = sum_1 + i;
  }
 
  int sum_2 = 0;
  int j = 1;
  for (; j < n; ++j) {
    sum_2 = sum_2 + j;
  }
 
  return sum_1 + sum_2;
}

代码中,m和n表示两个数据规模,我们无法事先评估m和n谁的量级大,所以上面代码的时间复杂度就是O(m+n)。

(4)O(n)

for(i=1; i<=n; i++) {
   j = i;
   j++;
}

(5)O(n^2)

for(x=1; i <= n; x++){
   for(i = 1; i <= n; i++) {
       j = i;
       j++;
    }
}

二分查找的时间复杂度:logn
https://www.cnblogs.com/yellowgg/p/11272908.html

参考:
https://juejin.cn/post/6844904167824162823
https://juejin.cn/post/6844904087876534280

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值