标签:
链表、Hash、双指针
题目:
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
数据范围:链表长度 0 \le n \le 100000≤n≤10000,链表中任意节点的值满足 |val| <= 100000∣val∣<=100000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
输入分为两部分,第一部分为链表,第二部分代表是否有环,然后将组成的head头结点传入到函数里面。-1代表无环,其它的数字代表有环,这些参数解释仅仅是为了方便读者自测调试。实际在编程时读入的是链表的头节点。
例如输入{3,2,0,-4},1时,对应的链表结构如下图所示:
可以看出环的入口结点为从头结点开始的第1个结点(注:头结点为第0个结点),所以输出true。
输入:{3,2,0,-4},1
返回值:true说明:第一部分{3,2,0,-4}代表一个链表,第二部分的1表示,-4到位置1(注:头结点为位置0),即-4->2存在一个链接,组成传入的head为一个带环的链表,返回true
输入:{1},-1
返回值:false说明:第一部分{1}代表一个链表,-1代表无环,组成传入head为一个无环的单链表,返回false
反思:
方法一:使用Hash
首先我们可以创建一个HashSet集合来作为缓存(无序,不重复)来存储曾遍历过的节点,然后同样从头结点开始,依次遍历链表中的下一个节点,如果Hash集合中没有下一个节点,则我们把该节点存放到我们的Hash集合中,如果存在,则直接返回true。
方法二:使用双指针
对于链表,我们一个节点只能指向下一个节点,不能指向两个节点,所以我们可以先说一下线性链表的其中一个性质:普通的线性链表的末尾一定有null,那么我们就可以根据链表中是否有null判断是不是有环。而环型链表的环一定在末尾,末尾没有null了。
但是,唤醒链表遍历过程中会不断循环,线性链表遍历到null就结束了,但是环形链表何时才能结束呢?我们可以使用双指针的技巧,同向访问的双指针,速度是快慢的,只要有环,两者会在环内不断循环,因为两者速度有差异,两者必定会相遇。
1、设置快慢指针,初始值都指向链表头。
2、遍历链表,快指针每次走两步,慢指针每次走一步。
3、如果快指针到了链表的末尾,说明没有环,因为它每次走两步,所以快指针会在环内追到慢指针,两者相遇代表有环。
用到的知识点:
链表、HashSet集合、双指针
代码:
链表定义
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
方法一:使用Hash
public boolean hasCycle1(ListNode head) {
HashSet<ListNode> listNodes = new HashSet<ListNode>();
listNodes.add(head);
while (head != null && head.next != null) {
if (!listNodes.contains(head.next)) {
listNodes.add(head.next);
} else {
return true;
}
head = head.next;
}
return false;
}
public boolean hasCycle2(ListNode head) {
// 先判断当前节点是否为null
if (head == null) {
return false;
}
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = head.next.next;
slow = head.next;
if (fast == slow) {
return true;
}
}
return false;
}