1. 判断链表中是否有环?
1.1 第一种方法使用Hash,遍历的时候将元素放入map中,如果有环,一定发生碰撞,发生碰撞的位置就是入口的位置。
代码如下:
其中ListNode类。和力扣中的定义一样
public ListNode hashMap(ListNode head){
ListNode pos = head;
//1.创建map
HashSet<ListNode> visited = new HashSet<>();
//2.边遍历边存边比较是否包含
while(pos != null){
if (visited.contains(pos)){
return pos;
}else {
visited.add(pos);
}
pos = pos.next;
}
//返回null代表没有环
return null;
}
1.2 为什么快慢指针一定相遇?
确定是否有环,最有效也是最高级的方法,用双指针,一个快指针(一次走两步),一个慢指针(一次走一步),如果有环两个人一定会相遇。举例:操场跑圈,跑的快的同学会追上跑得慢的同学。
代码:
//返回true代表有环
public boolean shuangzhizhen(ListNode head){
//1.排除空元素或者只有一个元素
if (head == null || head.next ==null){
return false;
}
//2.定义两个指针指向头部,从头遍历
ListNode fast = head;
ListNode slow = head;
//3.快指针走两部,慢指针走一步
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
return true;
}
}
return false;
}
1.3 确定入口的方法
这个问题搞懂了会很有趣,
先说结论:按快慢指针找到相遇位置(假设图中的Z),然后将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度前进,则两个指针在开始位置相遇(Y).
(1)先看一圈就遇到的情况
在快指针第二次进入环的位置就相遇了:
1. fast,slow快慢指针,fast每走两步,slow走一步,知道在环中相遇,这个在问题开头也提到过。
2.第一次相遇:
fast走了:a + b + c +b
slow走了: a+b
计算:2*(a+b) = a + b + c +b 可得:a = c
因此,slow从Z出发,fast从头出发,相同的速度,一次一步,走相同的距离,会在Y处相遇。
(2)多圈之后相遇,
结论:相遇的时候快指针已经转l(n-1)圈,同样是上面的场景找入口位置,再写一次(按快慢指针找到相遇位置(假设图中的Z),然后将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度前进,则两个指针在开始位置相遇(Y).)n = 1,2,3...
计算:
fast走了:a + n *(b+c) +b
slow走了:a + b
化简可得:a = c+ (n - 1)(a + b),因为b+c是一圈 LEN,则:
a= c+(n-1)LEN
又回到了上面a=c的场景,快指针会多走(n-1)圈两者相遇,再以相同的速度转圈,相遇的位置即是入口的位置,
代码实现:
public ListNode cycle(ListNode head){
if (head == null){
return null;
}
ListNode fast = head, slow = head;
//为null则没有环
while (fast != null){
slow = slow.next;
if (fast.next != null){
fast = fast.next.next;
}else {
return null;
}
if (fast == slow){
//让一个指针从头开始,一个指针从相遇位置开始
ListNode ptr = head;
while (ptr != slow){
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
1.4 第二种确认入口的方法,
这种方法代码实现较为复杂,用了三次双指针,
思想:如果我们确定连环的大小和末尾结点,该问题就退化成找倒数第K个结点的问题,
问题1: 判断环的大小,首先判断环的存在,快慢指针相遇,就证明有环,假设相遇点Z,让fast和slow,让 fast固定在该位置,slow遍历,当fast = slow 时,slow走过的距离就是环的长度
问题2:如何确定末尾结点,让快指针先走环的长度距离,然后慢指针从头开始走,此时快指针走到入口位置的时候,慢指针正好也走到入口的位置,
可以看图根据走的距离来想这个问题,看图,红色的线是快指针走的距离,蓝色线是慢指针走的 距离,蓝色少走了一个环的长度
使用的是找倒数第k结点的方法来找入口
代码实现:
public static ListNode cycle(ListNode head){
//1.判断环找相遇点Z,第一次双指针
if (head == null || head.next ==null){
return null;
}
ListNode fast = head;
ListNode slow = head;
//相遇的结点
ListNode z = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
z =fast;
}
}
//2.计算环的长度,第二次双指针
//先走一步位置错开,好判断,len从1开始
fast = fast.next;
int len = 1;
while(fast != z){
fast =fast.next;
len++;
}
//3.使用倒数第K个结点的方法来找入口,第三次双指针
slow = head;
fast = head;
while (len>0){
fast = fast.next;
len--;
}
while (fast != slow){
fast =fast.next;
slow =slow.next;
}
return slow;
}