数据结构与算法问题的难度完全取决于你所申请的公司
数组由一组相同的数据类型组成。它存储在连续的内存空间内,使用索引可以找到元素的地址。数组包括一维数组和多维数组,一维数组是最简单的数据结构,也是最常用的。
算法 | 平均 | 最坏 |
---|---|---|
空间(Space) | O(n) | O(n) |
查找(Search) | O(n) | O(n) |
插入(Insert) | O(n) | O(n) |
删除(Delete) | O(n) | O(n) |
链表看起来更像树,而不是数组,它使用一组结点来表示一个序列。每一个结点都包含数据和一个指针。在链表中,结点中的数据可以为任意类型,而指针则是指向下一结点的引用。链表包含一个头结点和一个尾结点。头结点是链表中的第一个结点,尾结点是最后一个结点。链表不是一个循环数据结构,所以尾结点没有指向头结点的指针,指针为空。一些基础方法的时间复杂度如下:
算法 | 平均 | 最坏 |
---|---|---|
空间 (Space) | O(n) | O(n) |
查找 (Search) | O(n) | O(n) |
插入 (Insert) | O(1) | O(1) |
删除 (Delete) | O(1) | O(1) |
一个双向链表首先是一个链表,但是在每个结点中有两个指针,前驱指针指向前驱结点,后继指针指向后继结点。双向链表也有一个头结点,头结点的后继指针指向第一个结点。最后一个结点的后继指针指向空,但是如果最后一个结点的后继指针指向第一个结点,这时称这个链表为双向循环链表。双向循环链表能非常方便地从每个结点查找它的前驱结点和后继结点。
算法 | 平均 | 最坏 |
---|---|---|
空间 (Space) | O(n) | O(n) |
查找 (Search) | O(n) | O(n) |
插入 (Insert) | O(1) | O(1) |
删除 (Delete) | O(1) | O(1) |
栈是一个有着「后进先出」特性的基础数据结构,这就意味着最后一个入栈的元素,也是第一个出栈的。栈就像是一堆书,想要得到书堆中的第一本书(最下面一本),必须把其他的书都先拿走。向栈中添加一个元素的操作被称为 Push(入栈),删除一个元素的操作被称为 Pop(出栈),查看且不删除最后一个入栈的元素的操作被称为 Top 。实现栈的常用方法是使用链表(LinkedList),也可以使用不允许空值的 StackArray(使用数组实现),还有允许空值的 Vector
算法 | 平均 | 最坏 | 图形表示 |
---|---|---|---|
空间 (Space) | O(n) | O(n) | ![]() |
查找 (Search) | O(n) | O(n) | |
入栈 (Push) | O(1) | O(1) | |
出栈 (Pop) | O(1) | O(1) | |
查看栈顶 (Top) | O(1) | O(1) |
所谓贪心算法(贪婪算法),是在求解一个问题时,作出当前最好的选择,而不考虑大局。也就是说,这种算法的每一种实现,只是在作出一个某种方面上的局部最优解。
我们可以说贪心算法是「短视的」, 「不可恢复」的
此算法没有固定框架,只有每个人贪心策略的选择,而导致的不同姿态的代码实现。
来自wikipedia:
贪心法,又称貪心演算法、貪婪演算法、或稱貪婪法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。[1]比如在旅行推销员问题中,如果旅行员每次都选择最近的城市,那这就是一种贪心算法。
贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
贪心法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码……对于其他问题,贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。
面向对象编程是使用类,对象,继承性,多态性,封装性和抽象的一种程序设计方法。。
在面向对象的概念中,所有对象都是由类来描述,但是反过来,并不是所有类都是用来描述对象的。如果一个类中没有包含足够信息来描绘一个具体的对象,这样的类就是抽象类。
继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。 有些编程语言支持多重继承,即一个子类别可以同时有多个父类别,比如C++编程语言;而在有些编程语言中,一个子类别只能继承自一个父类别,比如Java编程语言,这时可以利用接口来实现与多重继承相似的效果。 现今面向对象程式设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“型态”,使得元件的型态一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在执行期动态扩充行为的遗憾。
从字面上理解就是包装的意思,是指利用抽象数据类型,将数据和关于数据的操作封装起来,使其成为一个不可分割的独立实体。数据将会被保护在抽象数据类型的内部,仅能够通过暴露在表面的操作(public方法,比如setter和getter)来与这个对象进行交流和交互。用户不知道对象的内部细节,但是通过该对象提供的接口来访问对象。其好处是:减少耦合,方便地在未来修改调整自己,更加有把握地(精确地)控制成员,隐藏信息,实现细节。
使用相同的消息,使得类作出不同的反应(继承为我们使用多态打下了基础)。Java实现多态有三个必要条件:继承、重写、向上转型。
封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
多态: 多态性是指允许不同类的对象对同一消息作出 响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
Liskov替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。
普通的类可以自然地实例化他自己,相反地,内部类是这样的类: 一定要绑定上一个外部类才能进行实例化的类。
内部类提供了一种模拟车和车轮的机制,车是外部类,车轮是内部类
而匿名内部类也就是没有名字的内部类,因为没有名字,所以只能使用一次。
使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
详情可见:https://www.cdsy.xyz/computer/programme/java/250315/cd73896.html
== 比较两个字符串的地址,初学者很经常拿来比较其内容,将会导致出现不等的情况。 equals()是String这个类重写的一个方法,平常的类的equals()也仅仅是比较两个变量的地址,而String类的equals()重写后,将依次比较其串中的字符。
一般是在想要人性化地(而不是计算机式地,比较地址那样)比较两个对象的时候,我们需要使用这两个方法,或者说我们要重写这两个方法,而且有如下的原则:
hashCode请参考:https://www.cdsy.xyz/computer/programme/java/250315/cd73897.html
总的来说是:保留下来却永远不再使用的对象引用 它包括: 1. 静态集合类引起内存泄漏, 2. 当集合里面的对象属性被修改后,再调用remove()方法时不起作用, 3. 监听器, 4. 各种连接 5. 内部类和外部模块的引用 6. 单例模式 详情可见:https://www.cdsy.xyz/computer/programme/java/230513/cd43552.html
而关于Java 如何处理它,我还不能给出准确答案,应该是使用各种检测泄漏的工具检测到之后,使用GC处理它们
Arrays:一个包含许多和操纵数组有关方法的类,比如排序和查找,它继承自Object类。
ArraysList:是一个容器,它可以实现数组的大小可变,方便地增加和删除元素。它实现了List接口的类。
详细的笔记在:https://www.cdsy.xyz/computer/programme/java/20210305/cd16149329499297.html
TreeSet是基于二叉树实现的,其中的数据是自动排序好的。不允许放入null值。
HashSet是基于Hash表实现的,其中的数据是无序的,允许放入null值。
详细的笔记在:https://www.cdsy.xyz/computer/programme/java/20210307/cd161510473910999.html
1. 基本数据的类型转换
在Java中,整数类型(byte/short/int/long)中,对于未声明数据类型的整形,其默认类型为int型。在浮点类型(float/double)中,对于未声明数据类型的浮点型,默认为double型。而当我们将一个数值范围较小的类型赋给一个数值范围较大的数值型变量,jvm在编译过程中会将此数值的类型进行自动提升。在数值类型的自动类型提升过程中,数值精度至少不应该降低(整型保持不变,float->double精度将变高)。而当我们需要将数值范围较大的数值类型赋给数值范围较小的数值类型变量时,我们需要手动地转换,称为强制类型转换。
2.引用数据类型的类型转换
由于继承和向上转型,子类向父类的转换是很自然的。但是当把父类转换为子类时,强制类型转换会在运行时检查父类的真实类型:如果引用的父类对象的真实身份是子类类型,只不过这个子类类型的对象经历了向上转型变成父类对象,这个时候我们再向下转回来子类,是可以的;而如果真的是父类的类型,就会抛出ClassCastException的异常。
详细的笔记在:https://www.cdsy.xyz/computer/programme/java/20210305/cd16149327529133.html
诸如public,protected,private这几个关键词叫做访问修饰符。
其作用是控制它所定义的域或者方法的访问权。
可以,通过继承,你可以
static是Java里的非访问修饰符,它可以用来创建类方法和类变量。
当修饰一个变量的时候,此变量就成了独立于对象的静态变量,无论一个类实例化了多少个对象,这个类只有一份这个静态变量的拷贝,所以static修饰的变量,即静态变量,也被叫做类变量。一个局部变量不能被声明为static变量。
当修饰一个方法的时候,此方法就成了独立于对象的静态方法,静态方法不能使用类的非静态变量,因为静态方法和静态变量先于非静态的其他成员初始化,静态方法先出来,然后才是非静态的,所以明白这个顺序很重要。静态方法从参数列表得到数据,然后计算这些数据。
有些人认为static方法不是面向对象的,因为它们确实具有全局函数的语义;使用static方法时,由于不存在this,所以不是通过「向对象发送消息」的方式来完成的。当代码中出现很多static方法时,你应该反思自己的设计的,然而,static确有其用。
总结一下,当 static:
严格来说,不存在静态方法的重写,当一个子类继承父类时,写同样的方法时,只是将父类的静态方法隐藏。
[静态方法能否被重写](https://www.cdsy.xyz/computer/programme/java/20221016/cd166587529837163.html
所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。 专业术语有严格的含义,用语要准确."重写"只能适用于实例方法.不能用于静态方法.对于静态方法,只能隐藏. 静态方法的调用不需要实例化.. 不实例化也就不能用多态了,也就没有所谓的父类引用指向子类实例.因为不能实例化 也就没有机会去指向子类的实例。所以也就不存在多态了。
允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)
Java中的对象总是以值传递的。
- private void init(MyClass objVar) {
- objVar = new MyClass();
- }
- public void test() {
- MyClass obj = null;
- init(obj);
- //在调用init方法后,obj仍旧指向空
- //这是因为,obj是值传递,而不是引用传递。
- }
-
-
请参考这里:https://www.cdsy.xyz/computer/programme/java/230513/cd43556.html
本地变量就是局部变量,它在方法或者代码块里被声明并使用,其内存中的位置是栈里,没有默认初始化值,生命周期很短。 实例变量是没有被static修饰的成员变量,它属于一个类的一个实例。每次new一个实例,这样的变量也同时new一遍,其位置在堆区中,有默认初始化的值,生命周期和它所在的实例一样长。 类变量,又称静态变量,它是被static修饰的成员变量,它属于一个类,被所有实例共享。每次new一个实例,这样的变量并不会被new一遍,其内存位置在方法区内。可以通过类名直接访问。有默认的初始化值,生命周期很长。
强引用:不会被GC轻易清理,只要引用存在,垃圾回收器永远不会回收。
Object obj = new Object();
软引用: 非必须引用,内存溢出之前进行回收
- Object obj = new Object();
- SoftReference<Object> sf = new SoftReference<Object>(obj);
- obj = null;
- sf.get();//有时候会返回null
-
弱引用: 第二次垃圾回收时回收
- Object obj = new Object();
- WeakReference wf = new WeakReference(obj);
- obj = null; wf.get();
- //有时候会返回null
- wf.isEnQueued();
- //返回是否被垃圾回收器标记为即将回收的垃圾
虚引用: 垃圾回收时回收,无法通过引用取到对象值
- Object obj = new Object();
- PhantomReference<Object> pf = new PhantomReference<Object>(obj);
- obj=null;
- pf.get();//永远返回null
- pf.isEnQueued();//返回是否从内存中已经删除
-
具体笔记可参考:https://www.cdsy.xyz/computer/programme/java/230820/cd45596.html
以及:https://www.cdsy.xyz/computer/programme/java/230826/cd45685.html
详细请看:https://www.cdsy.xyz/computer/programme/spring/20201211/cd16076616015708.html
易式修饰符
出现它的原因是,java的多线程中存在两个或多个线程时间间隔很短地访问共享成员变量(指被多个线程共享的变量),在每个线程自己的工作内存中,可能对这个变量进行修改,但是没有及时将工作内存中的变量(对原本的共享成员变量的一份拷贝)写回共享成员变量,此时,当另外一个线程进行读取时,将无法得到最新的此变量,导致进程的工作不能正确进行。
于是出现了volatile,带有volatile修饰的变量,就是当其在某个线程自己的工作内存中发生改变时,会被强制地,写回公共成员变量所在的公共内存处。
如此便保证了,所有线程对这个变量的访问都是能得到此变量最新状态的访问。
transient是一个类型修饰符,仅仅能用来修饰字段(变量)。在此字段所在的对象进行序列化的时候,这个字段不会被序列化。
其他没有transient修饰的变量将会被序列化,然后进行传输,或者存储到本地磁盘,transient变量就在这个过程里丢失了
实例化着重于动作,强调==对象==从无到右的创建过程,而初始化着重于状态,强调==对象的特征==从无到有的过程。
初始化的顺序大致是这样的:
1. 为类中的静态变量分配好空间(同时给变量以默认值) 2. 如果创建了类的对象,那么为类的实例变量分配地址空间(同时给变量以默认值)
就在这两步之间,发生了静态块的运行
StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对String的改变都 会引发新的String对象的生成;StringBuffer和StringBuilder则是可变类
先说一下,以集合为例,HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。
一般来说,这是我们创建一个类的实例时,我们会这样:
- MyClass a = new MyClass();
-
但是当我们新建一些支持自动装箱和拆箱的数据类型的时侯(比如Integer,基本数据类型的包装类),我们可以这样:
- Integer i = 100;
-
执行这句语句,将会被编译器执行为:
- Integer i = Integer.valueOf(100);
-
这就是自动装箱
自动拆箱(unboxing):
将对象中的基本数据从对象中自动取出
比如:
- Integer i = 10;//autoboxing
- int c = i;//unboxing
-
创建 onCreate - 启动 onStart – 开始 onResume – 暂停 onPause – 结束 onStop – 销毁 onDestroy
每一个活动( Activity )都处于某一个状态,对于开发者来说,是无法控制其应用程序处于某一个状态的,这些均由系统来完成。 但是当一个活动的状态发生改变的时候,开发者可以通过调用 onXX() 的方法获取到相关的通知信息。
在实现 Activity 类的时候,通过覆盖( override )这些方法即可在你需要处理的时候来调用。
Android 中,Activity是所有程序的根本,所有程序的流程都运行在Activity 之中,Activity可以算是开发者遇到的最频繁,也是Android 当中最基本的模块之一。在Android的程序当中,Activity 一般代表手机屏幕的一屏。如果把手机比作一个浏览器,那么Activity就相当于一个网页。在Activity 当中可以添加一些Button、Check box 等控件。可以看到Activity 概念和网页的概念相当类似。一般一个Android 应用是由多个Activity 组成的。这多个Activity 之间可以进行相互跳转,例如,按下一个Button按钮后,可能会跳转到其他的Activity。
和网页跳转稍微有些不一样的是,Activity 之间的跳转有可能返回值,例如,从Activity A 跳转到Activity B,那么当Activity B 运行结束的时候,有可能会给Activity A 一个返回值。这样做在很多时候是相当方便的。 当打开一个新的屏幕时,之前一个屏幕会被置为暂停状态,并且压入历史堆栈中。用户可以通过回退操作返回到以前打开过的屏幕。可以选择性的移除一些没有必要保留的屏幕,因为Android会把每个应用的开始到当前的每个屏幕保存在堆栈中。
Service 是android 系统中的一种组件,它跟Activity 的级别差不多,但是他不能自己运行,只能后台运行,并且可以和其他组件进行交互。Service 是没有界面的长生命周期的代码。Service是一种程序,它可以运行很长时间,但是它却没有用户界面。这么说有点枯燥,来看个例子。
打开一个音乐播放器的程序,这个时候若想上网了,那么,打开Android浏览器,这个时候虽然已经进入了浏览器这个程序,但是,歌曲播放并没有停止,而是在后台继续一首接着一首的播放。其实这个播放就是由播放音乐的Service进行控制。
当然这个播放音乐的Service也可以停止,例如,当播放列表里边的歌曲都结束,或者用户按下了停止音乐播放的快捷键等。Service 可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD 卡上文件的变化,再或者在后台记录地理信息位置的改变等等,总之服务嘛,总是藏在后头的。
开启 Service 有两种方式:
Context.startService()
Service会经历onCreate -> onStart(如果Service还没有运行,则android先调用onCreate()然后调用onStart(); 如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次 );
StopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用StopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。 *注意:多次调用Context.startservice()不会嵌套(即使会有相应的onStart()方法被调用),所以无论同一个服务被启动了多少次,一旦调用Context.stopService()或者StopSelf(),他都会被停止。 补充说明:传递给StartService(0的Intent对象会传递给onStart()方法。调用顺序为:onCreate --> onStart(可多次调用) --> onDestroy。
Context.bindService()
Service会经历onCreate() -->onBind(),onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind --> onDestroyed相应退出,所谓绑定在一起就共存亡了。
在Android 中,Broadcast是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver 是对发送出来的Broadcast进行过滤接受并响应的一类组件。可以使用BroadcastReceiver 来让应用对一个外部的事件做出响应。这是非常有意思的,例如,当电话呼入这个外部事件到来的时候,可以利用BroadcastReceiver 进行处理。
例如,当下载一个程序成功完成的时候,仍然可以利用BroadcastReceiver 进行处理。BroadcastReceiver不能生成UI,也就是说对于用户来说不是透明的,用户是看不到的。
BroadcastReceiver通过NotificationManager 来通知用户这些事情发生了。BroadcastReceiver 既可以在AndroidManifest.xml 中注册,也可以在运行时的代码中使用Context.registerReceiver()进行注册。只要是注册了,当事件来临的时候,即使程序没有启动,系统也在需要的时候启动程序。各种应用还可以通过使用Context.sendBroadcast () 将它们自己的Intent Broadcasts广播给其他应用程序。
Content Provider 是Android提供的第三方应用数据的访问方案。
在Android中,对数据的保护是很严密的,除了放在SD卡中的数据,一个应用所持有的数据库、文件等内容,都是不允许其他直接访问的。Andorid当然不会真的把每个应用都做成一座孤岛,它为所有应用都准备了一扇窗,这就是Content Provider。应用想对外提供的数据,可以通过派生Content Provider类, 封装成一枚Content Provider,每个Content Provider都用一个uri作为独立的标识,形如:content://com.xxxxx。所有东西看着像REST的样子,但实际上,它比REST 更为灵活。
和REST类似,uri也可以有两种类型,一种是带id的,另一种是列表的,但实现者不需要按照这个模式来做,给id的uri也可以返回列表类型的数据,只要调用者明白,就无妨,不用苛求所谓的REST。