环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例1
输入:head= [3,2,0,-4] ,pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例2
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例3
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
所需知识
何为链表:
通俗的理解就是每个节点由两部分组成,一部分为值,也就是数据,另一部分为指向下一节点的地址。通过指向下一节点的地址将数据串联起来。
基本概念
链表实际上是线性表的链式存储结构,与数组不同的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,且链表的长度不是固定的,链表数据的这一特点使其可以非常的方便地实现节点的插入和删除操作
链表的每个元素称为一个节点,每个节点都可以存储在内存中的不同的位置,为了表示每个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构
除了存储元素本身的信息外,还要存储其直接后继信息,因此,每个节点都包含两个部分,第一部分称为链表的数据区域,用于存储元素本身的数据信息,这里用val表示,
它不局限于一个成员数据,也可是多个成员数据,第二部分是一个结构体指针,称为链表的指针域,用于存储其直接后继的节点信息,这里用next表示
next的值实际上就是下一个节点的地址,当前节点为末节点时,next的值设为空指针
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
方法一
思路
每次循环比遍历一个节点时,将节点存储在一个set集合中,在遍历每个节点之前,判断set集合中是否已存在这个节点数据,若已存在,返回true,若循环完毕没有返回,则表示不是循环节点,返回false。
代码
public boolean hasCycle(ListNode head) {
// 每一次遍历一个节点 就用容器承装 下一次判断此节点是否在容器中
Set<ListNode> set = new HashSet<>();
// 直到head不为空
while (head != null) {
// 如果set中存在这个head则证明这是一个环形链表
if (set.contains(head)) {
return true;
}
set.add(head);
head = head.next;
}
return false;
}
方法二(优化空间)
思路
快慢指针:快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。
这只两个指针,一个快指针(每次向下移动两个节点),一个慢指针(每次向下移动一个节点),如果快慢指针相遇,则说明是环形链表,如果不相遇则说明不是环形链表。
例如:50米短跑比赛,赛道是一个直线型,则选手之间不会发生相遇,但4000米比赛,场地是原型的(假设400米一圈),那么当跑得快的选手超过跑的慢的选手一圈时,两人则会相遇。
代码
public boolean hasCycle2(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (fast != slow) {
if (fast == null || fast.next == null) {
// fast==null 说明直接到了空节点 fast.next==null 说明到了最后一个几点
return false;
}
// 声明快慢指针
slow = slow.next;
fast = fast.next.next;
}
return true;
}