面试的时候经常会遇到要判断一个链表是否有环,今天就来总结一下如何判断一个链表是否有环,以及链表环长、链表的入环点。
首先我们来看一下如何判断链表是否有环
1.利用哈希表
- 我们可以在遍历的时候将每一个链表节点存入到哈希表中,然后在遍历的过程中寻找是否有这个节点了,如果有,那就是环形链表,如果没有,继续遍历,直到链表节点为空(null)。这个思路很简单也很容易懂,我们直接看代码。
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();//这边利用set集合
while (head != null) {
if (set.contains(head)) {//如果包含了链表节点,那么我们就直接返回true
return true;
} else {
set.add(head);
}
head = head.next;
}
return false;
}
2.利用双指针(快慢指针法)
- 我们定义一个慢指针slow(每次前进1步),一个快指针fast(每次前进2步),刚开始的时候他们都指向头结点。根据以前学的知识,如果是个环形链表,快指针一定会在某一时刻追上慢指针,并且在他们首次相遇的时候,快指针一定比慢指针多移动了一圈!
是不是很简单?很多算法中经常会用到快慢指针,下面附上代码:
public boolean hasCycle(ListNode head) {
if(head == null){
return false;
}
ListNode slow = head;
ListNode fast = head;
//因为如果链表无环,那么一定是快指针先到达null
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(fast == slow){
return true;
}
}
return false;
}
接着我们来看一下如何判断环形链表的长度
- 我们定义的快指针恰好比慢指针每次多走1步,并且他们首次相遇的时候,快指针比慢指针多跑了1圈,那么
环 长 = 快 指 针 前 进 的 步 数 − 慢 指 针 前 进 的 步 数 环长=快指针前进的步数-慢指针前进的步数 环长=快指针前进的步数−慢指针前进的步数
public class Main{
static class Node{
int data;
Node next;
Node(int data){
this.data = data;
}
}
public static void main(String[] args) {
Node node1 = new Node(5);
Node node2 = new Node(2);
Node node3 = new Node(1);
Node node4 = new Node(7);
Node node5 = new Node(4);
Node node6 = new Node(3);
Node node7 = new Node(8);
Node node8 = new Node(9);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = node8;
node8.next = node3;
System.out.println(nodeLen(node1));
}
//判断环长
public static int nodeLen(Node head){
Node slow = head;
Node fast = head;
int slow_count = 0;//记录慢指针前进的步数
int fast_count = 0;//记录快指针前进的步数
while(fast != null && fast.next != null){
slow = slow.next;
slow_count++;
fast = fast.next.next;
fast_count += 2;
if(fast == slow){
return fast_count - slow_count;
}
}
return 0;
}
}
接着我们来看一下如何判断环形链表的入环点
- 我们试想一下,如果定义起点到入环点的距离为d,入环点到快慢指针首次相遇的点为s1,首次相遇点到入环点的距离为s2,那么我们可以得出以下结论:当快慢指针相遇的时候,
慢指针假设行动了:
d 1 = d + x ∗ ( s 1 + s 2 ) + s 1 d1=d+x*(s1+s2)+s1 d1=d+x∗(s1+s2)+s1
快指针行动了:
d 2 = d + n ∗ ( s 1 + s 2 ) + s 1 d2=d+n*(s1+s2)+s1 d2=d+n∗(s1+s2)+s1
因为我们这里快指针每次是慢指针行动的2倍,即:
2 ∗ d 1 = d 2 2*d1=d2 2∗d1=d2
所以两个公式整理得:
d = ( n − 2 x − 1 ) ( s 1 + s 2 ) + s 2 d=(n-2x-1)(s1+s2)+s2 d=(n−2x−1)(s1+s2)+s2
也就是说:
链 表 的 起 点 到 入 环 点 的 距 离 = 首 次 相 遇 点 开 始 绕 环 行 动 ( n − 2 x − 1 ) 圈 之 后 + 首 次 相 遇 点 到 入 环 点 的 距 离 。 链表的起点到入环点的距离=首次相遇点开始绕环行动(n-2x-1)圈之后+首次相遇点到入环点的距离。 链表的起点到入环点的距离=首次相遇点开始绕环行动(n−2x−1)圈之后+首次相遇点到入环点的距离。 - 我们可以假设n=2x+1(因为绕环跑了0、1、2、3…m圈的结果都是一样的,最后都是加上s2的长度,这里我们就可以假设绕环行动了0圈),那么我们就可以在找到首次相遇点的时候,定义一个指针指向链表的起点,一个指针指向首次相遇点,然后两个指针每次前进1步,当两个指针相遇的时候就是链表的入环点。
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null){
return null;
}
ListNode slow = head;
ListNode fast = head;
ListNode first = null;
ListNode now = null;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
first = head;
now = slow;
break;
}
}
if(first != null && now != null){
while(first != now){
first = first.next;
now = now.next;
}
if(first == now){
return now;
}
}
return null;
}