描述
给定一个单链表,是否有环未知,涉及到的问题有:
- 判断其中是否有环的存在;
- 如果存在环,找出环的入口点;
- 如果存在环,求出环上节点的个数;
- 如果存在环,求出链表的长度;
- 如果存在环,求出环上距离任意一个节点最远的点(对面节点);
- (扩展)如何判断两个无环链表是否相交;
- (扩展)如果相交,求出第一个相交的节点。
第一个问题:判断其中是否有环的存在
相关链接:环形链表判断是否有环
解法:一般解法、集合法、双指针法、特殊解法
一般解法:穷举法
思路:将每个节点标注序号,每遍历到一个节点,从头节点序号到该节点序号中间是否存在该节点。如果存在,就存在环,如果没有,继续遍历下一个节点直到完成整个链表。
分析:
时间复杂度:O(n2)
空间复杂度:O(1)
缺点:无法保留以前遍历的节点的信息。
集合法:保留以往遍历节点的信息
思路:每遍历一个节点,判断该节点是否存在于map中,如果不存在,将该节点存入map中,如果存在,证明有环.
分析:
时间复杂度:O(n)
空间复杂度:O(n)
代码:
//通过集合方法
public boolean hasCycle_set(ListNode head) {
//1.每到一个节点,检查是否在集合中
//2. 如果在集合中,返回true;如果不在,将节点加入到集合中
//3. 重复1-2过程直到没有后继节点,返回false
// 时间复杂度 O(n),空间复杂度O(n)
Map<ListNode,Integer> map = new HashMap<>();
int index = 0;
while (head!=null)
{
if (map.containsKey(head)) return true;
map.put(head,index++);
head = head.next;
}
return false;
}
双指针
思路:设置1个快指针,1个慢指针,同时从头节点出发,由于快慢指针之间有速度差,如果有环,两指针迟早会相遇。
代码:
public boolean hasCycle_num(ListNode head){
// 1. 设置两个指针,一快一慢,如果存在环,快慢指针一定会相遇;
// 2. 设置慢指针走1步,快指针走2步(只要大于1步)
// 3. 时间复杂度O(n), 空间发咋读O(1)
if (head == null) return false;
ListNode quick = head;
ListNode slow = head;
slow = slow.next;
quick = quick.next;
quick = quick==null?null:quick.next;
while (quick !=null && slow != null)
{
if (quick == slow) return true;
slow = slow.next;
quick = quick.next;
quick = quick==null?null:quick.next;
}
return false;
}
分析:
时间复杂度:O(n)
空间复杂度:O(1)
特殊解法
思路:由于计算机申请堆内存是从低地址向高地址申请,如果能获得对象引用的地址信息,后继节点的地址信息比前节点的地址信息小于或等于,则证明存在环。
第二个问题:如果存在环,找出环的入口点
相关链接:环形链表 II
解法:穷举法、集合法、双指针法、特殊方法
穷举法
思路:如利用问题一穷举法,最先遍历到的环形节点就是环的入口。
集合法
思路:如果利用问题一集合法,第一次判断的存在节点即为环入口。
特殊方法
思路:如果利用问题一地址方法,第一次判断节点的后继节点即为环入口。
双指针法
思路:
- 前置条件:设头结点到入口的距离为a,两指针相遇点与入口的距离为x,慢指针行走的距离为s,环的长度为r.
- 由于两指针(慢走1步,快走2步)相遇,所以慢指针走s步,快指针走2s步;
- 根据距离关系
(1)s=a+x;
(2)2s=a+x+nr;
得到s=nr,这就很有意思了,快慢指针的步数差就是环长度的n倍; - 由距离公式a=s-x=nr-x,由于是环内绕圈,可将nr抽象成一圈,r,那么就可以得到s-x = r-x,意思就是说头节点与入口的距离刚好和相遇点继续往前走到入口的距离相等(或者是绕n圈后到入口的距离相等);
- 由4可知,将慢指针从头节点出发,快指针从相遇点出发,同时一步步走,最后的相遇点就是环入口。
代码
public ListNode hasCycle_num(ListNode head){
// 1. 设置两个指针,一快一慢,如果存在环,快慢指针一定会相遇;
// 2. 设置慢指针走1步,快指针走2步(只要大于1步)
// 3. 时间复杂度O(n), 空间发咋读O(1)
if (head == null) return null;
ListNode quick = head;
ListNode slow = head;
slow = slow.next;
quick = quick.next;
quick = quick==null?null:quick.next;
while (quick !=null && slow != null)
{
if (quick == slow) break;
slow = slow.next;
quick = quick.next;
quick = quick==null?null:quick.next;
}
if (quick == null) return null;
slow = head;
while (slow != quick)
{
slow = slow.next;
quick=quick.next;
}
return slow;
}
第三个问题:如果存在环,求出环上节点的个数
解法
双指针思路:
- 快慢指针从相遇点再次出发,再次相遇时慢指针走的步数就是环长度:设慢指针走x步,则再次相遇快指针走2x步,则有2x=n+x,得x=r,所以再次相遇慢指针刚好走了环长而且相遇在同一节点;
- 记录环入口节点,从入口节点出发,再次到入口节点,走的步长即为环长。
集合思路:
尾节点序号-头节点序号即为环长。
第四个问题:如果存在环,求出链表的长度
思路:由2,3个问题可以知道链表长度=头节点与入口的距离+环长。
第五个问题:如果存在环,求出环上距离任意一个节点最远的点(对面节点);
思路:从该节点走2/环长步,就可到达环上距该节点最远的节点
第六、七个问题:判断两个无环链表是否相交,且求出相交节点
问题链接:链表相交判断及求出相交点
解法:链表环的变式、特殊解法
链表环的变式
思路:
将其中一个链表(例如)首位相连,则问题变成了1,2问题。
特殊解法
思路:
- 设置A链表的非公共部分长度为a,B链表非公共部分长度为b,公共部分为x;
- 则a+x+b = b+x+a;
- 由2可知,如果A、B指针同时走,A走完走B路线,B走完走A路线,如果相交,两指针一定在公共节点处相交。
代码:
//O(n)时间复杂度 O(1)空间复杂度
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 设公共子节点 前面节点为c, A的节点数为 a,B 为 b
//当A走完 走B时, a走的走的步数有 a+b-c
//当B走完走A时, B走的步数有 b+a-c
//可知 a+b-c = b+a-c
//若有公共子节点,即c>0,最后返回的即是公共子节点
//若无公共子节点,即 c=0,最后返回的即是 null
ListNode A = headA, B = headB;
//若无公交节点,null!=null 为false,返回null
while (A!=B)
{
A = A==null?headB:A.next;
B = B==null?headA:B.next;
}
return A;
}