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