1.链表和数组的区别
链表和数组都是基本的数据结构,但是二者的存储方式不同。
数组:
数组 在内存中存储,需要一块连续的内存空间,对内存的要求较高,比如需要200MB大小的存储空间,那么这200MB必须是连续的。
链表:
链表的存储不需要连续的内存空间,它是通过指针来指定相应的内存空间,也就是通过“指针”,将一组零散的内存块串联起来。所以完全不用担心连续内存空间以及扩容等问题。
链表中的每个结点,都存储了下一个结点的内存地址的指针。所以链表在声明时,不需要和数组一样连续的内存空间,但是它需要额外的空间来存储与之关联的结点指针,通常就会认为,链表比数组会消耗更多的内存空间。
但是其实在我们正常的编码过程中,链表中存储的每个结点,其实是远大于指针存储所消耗的空间,这点消耗,基本上是可以忽略不计的。
2.链表的类型
链表根据指向的不同以及每个节点存储的指针关系,可分为:
- 单链表:每个结点存在一个 next 指针,称为后续指针, next 指针指向后一个结点,尾结点的 next 指针,指向 NULL。
- 单向循环链表:和单链表类似,但是尾结点的 next 指针,指向头结点。
- 双向链表:在单链表的基础上,为每个结点增加一个 prev 指针,指向该结点在链表中的前驱结点。
- ·双向循环链表:和双向链表类似,但是头结点的 prev 指向尾结点,而尾结点的 next 指向头结点,以此形成一个环。
单链表是最基本的链表,也是面试题中最常考的链表,通过单链表可以衍生出很多的面试题,而快慢指针就是常考的一类。
3.单链表的快慢指针
对于单链表,每个结点只有一个存储下一结点的 next 指针。当我们在查找某个结点的时候,无法和数组一样通过下标快速定位到待查询的结点,只能从头结点开始,通过每个结点的 next 指针,一个个结点的匹配查找。
这就代表了单链表循环的一个特点,它在遍历上无法后悔,遍历走过了,就是过了,无法回头(当然这里可以使用遍历两次的方法,但快慢指针可以只遍历一次)。除非再用一个数据结构去记录已经遍历的结点,那就不再是一个单纯的单链表了。
这就引发出一种解决方案,就是快慢指针,一个指针无法完成任务,那就再引入一个指针。它通过两个指针 fast 和 slow 指针,让两个指针保持一定的间隔规律,达到我们查找的目的。
4.快慢指针的例子
4.1判断链表中是否存在环
题目:已知一个单链表的 head,判断当前链表是否存在环。
在链表中,如果是在头节点就已经确定是个环的时候,只需要在循环的时候,判断尾结点的 next 是否指向头结点即可。
但是如果在环的入口不确定,它可能从任意一个结点开始形成了环的情况下就不这么简单了,这里我们就可以用快慢指针来进行判断。
fast 每次走两步而 slow 每次走一步,如果单链表中存在环,fast 以两倍于 slow 的速度前进,那么两个指针,最终一定会进入环,也一定会在环中相遇。
代码:
static boolean linkHasCircle(Node head){
Node