下图是数据结构所主要涉及的接口、抽象类和具体的类,黄色是接口、蓝色是抽象类、棕色是具体的类。这张图不需要背,只有有印象就可以,后面会一个一个讲:
时间复杂度和空间复杂度
对于一段代码,我们该如何去判断一段代码是好还是坏呢?一般就会根据时间复杂度和空间复杂度去判断。
推导大O阶方法就是一般我们用来计算时间复杂度的方法。我们一般是先将一段代码的执行次数先统计下来(不是指有多少行代码,是执行的次数),然后再使用大O阶去算最后的时间复杂度。
下面来看这个代码,它的时间复杂度是多少?
来看下解析:
所以,最后的答案就是N的平方。
再来看这几道题,答案是多少?
下面的这两道题相对就会难一些,用到了一些数学思想:
这里要注意;递归的 每次递归执行的次数 是指递归的每次调用(如计算、判断等这些都只被记作一次,但也有特殊情况)。
总结一下大O法的特点:
下来是空间复杂度,这个目前只做了解即可:
包装类
在JavaSE中,我们在讲基本数据类型的时候曾经提及过包装类的概念。再来回顾一下:
包装类的作用:由于基本数据类型(如int、double)不是对象,无法直接参与面向对象的操作。包装类(如Integer、Double)将这些基本类型封装为对象,使其能够参与继承、多态等特性。并且也会后面的泛型提供支持。
包装类一共能实现两种操作:装包和拆包。
Integer i = Integer.valueOf(a);这样子的属于显式装箱。
Integer i = 10;这种属于隐式装箱,由编译器来实现。
那将两组值来判断相不相等,结果分别是多少?
答案:true 和 false 。那么明明是同样的操作,究竟是为什么呢?
我们找到包装类的源码之后,发现了问题所在:
所以,包装类限定了范围。
泛型
来看这个例子:
如果<>括号中没有限定类型,它就既能够被整型调用,也可以被字符串调用,其他类型还可以调用;但是这样会显得代码较乱,没有条理性。所以我们一般都会限定类型。
我们可以这么去写,效果会更好一些。将每个类型翻开用泛型去调。
注意事项:
泛型全名泛型类,所以也可以去继承类。
然后关于裸类型,了解即可:
总结一下:
那么,泛型既然如此有用,它的内部是怎么进行编译的呢?
b
可以看到 pos val 都被相应换成了Object 中的东西。就“擦除”掉了。
还能给出一个小知识点:泛型的上界。
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。例如:
具体语法结构如下:
那么关于泛型上界,也有复杂示例下面来举一个例子:
提问:这里报错的理由是什么?
答:不确定<E>是什么类型的,有可能是Integer,也有可能是String;所以此时是无法直接用 == 来比较了。这时我们应该使用 compareTo 来比较其中的内容。
在使用 compareTo 之前,E必须是实现了Comparable接口的,否则是直接报错。
还有一种类似的写法,也看一下:
我们发现,这两种写法都需要实例化。那么问题来了,有没有不需要实例化的方法?
答:有的。定义静态方法,用类去直接引用就可以。
到此,主要内容就结束了。下来是一些例题。
这里的logn默认是指以2为底的。下同。
x答:A;此题目中,数组元素有序,所以a,b两个数可以分别从开始和结尾处开始搜,根据首尾元素的和是否大于sum,决定搜索的移动,整个数组被搜索一遍,就可以得到结果,所以最好时间复杂度为n。
答:A;从递推公式中可以看到,第n项的值需要从n-1开始递归,一直递归到0次结束,共递归了n-1次。所以时间复杂度为n。
答:D;此函数有一个循环,但是循环没有被执行n次,i每次都是2倍进行递增,所以循环只会被执行log2(n)次。所以不只是要看循环,也要结合代码其中的内容!
答:C;
该函数的作用是创建一个倒三角的二维数组:
第0行n个int的空间、第1行n-1个int的空间、第2行n-2个int的空间、...、第n-1行1个元素的空间
那么空间总的个数为:1+2+3+...+N-1 + N + N = (1+N)*N/2 + N = N^2/2 + 3N/2,采用大O渐进发表示就是:O(N^2)。
参考代码:
解题思路:直接将数组中所有元素之和sum,然后求出1~N的前N项和-sum即可。
参考代码1:
s
解题思路:就是正常思路。虽然这种方法可以,但是代码超时了,不是最优解。
参考代码2:
解题思路:先把整个数组反转,再分别反转前k
个和后n-k
个即可;例如[1,2,3,4,5]
, k = 3
, 先反转整个数组得[5,4,3,2,1]
,再反转前k
个得[3,4,5,2,1]
,再反转后n-k
个得[3,4,5,1,2]
即为答案。需要注意k
必须是小于n
的。
本篇文章到此结束!
本篇文章的截图和课件均摘自 比特科技 。希望能对你有帮助