环形链表相关题目
876 链表的中间节点
题目描述
思路 :快慢指针
快指针 fast 每轮走两步,慢指针每轮走一步,因此当快指针遍历完链表时,慢指针就指向链表中间节点。
链表长度为奇数: 当 fast 走到链表「尾节点」时,slow 正好走到「中间节点」。
链表长度为偶数: 当 fast 走到「null」时(越过「尾节点」后),slow 正好走到「第二个中间节点」。
总结以上规律,应在当 fast 遇到或越过尾节点 时跳出循环,并返回 slow 即可。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
}
若题目要求返回「第一个中间节点」,则应在 fast 遇到尾节点或其前驱节点 时跳出循环。此时,修改判断条件为 while fast.next and fast.next.next 即可。
141. 环形链表
题目描述
思路
如果链表有环,快慢指针,快指针走2步
,慢指针走1步
,肯定能够碰见;
如果没有环,那么会退出while循环
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
/**
使用快慢指针
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next==null) return false;
ListNode slow=head;
ListNode fast=head.next;
while(fast!=null && fast.next!=null){
if(slow==fast) return true;
slow=slow.next;
fast=fast.next.next;
}
return false;
}
}
142. 环形链表 II
题目描述
思路
快慢指针
快慢指针图解
我们先判断是否有环,然后再判断环的入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
相遇的时候,慢指针走的距离是 x+y,快指针走的距离是 x+y+n(y+z)
如果我们让快指针每次移动两步,慢指针移动一步,那么快指针走过的距离就是慢指针的 2 倍
所以 2(x+y)=x+y+n(y+z)
n(y+z)=x+y
x=(n-1)(y+z)+z
我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
哈希表
我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
public class Solution {
public ListNode detectCycle(ListNode head) {
//可以利用set的去重特性来解决
//set的add方法,如果元素添加成功则返回true
if(head==null || head.next==null) return null;
Set<ListNode> set=new HashSet();
ListNode p=head;
while(p!=null){
//只要set添加元素不成功,则说明这个结点之前已经添加过了
//这个结点就是重复的结点
if(set.contains(p){
return p;
}else{
p=p.next;
}
}
//说明链表不是循环链表
return null;
}
}
public class Solution {
public ListNode detectCycle(ListNode head) {
//可以利用set的去重特性来解决
//set的add方法,如果元素添加成功则返回true
if(head==null || head.next==null) return null;
Set<ListNode> set=new HashSet();
while(head!=null){
//只要set添加元素不成功,则说明这个结点之前已经添加过了
//这个结点就是重复的结点
if(!set.add(head)){
return head;
}else{
head=head.next;
}
}
//说明链表不是循环链表
return null;
}
}