给出一个单链表,节点结构如下
public class Node{
int value;
Node next;
}
要求:判断链表是否有环。若有环,返回入环的第一个节点;若无环,返回null。
快慢指针是链表问题中常用的方法,一个快指针一次走两步,一个慢指针一次走一步。这里利用双指针判断是否有环。无环的情况很简单,fast走得快,如果它遇到null就是无环的。注意判断next和next.next防止出现NullPointerException。
做过小学数学题的人都知道追及问题,小明和小红在操场上跑步,一个跑得慢一个跑得快,问小明在什么时候、什么位置遇到小红。有的题里两人同时出发,有的题里小明在小红之前或之后出发。这个问题实际上就是问小明什么时候把两人之间的距离缩短到0。这道题也一样。
假设链表有环,环周长L,环前面长度D。
若D=0,快指针F慢指针S同时在入环的第一个节点C出发,当F走了2L长度,S走了L长度,也就是F两圈,L一圈时,在C第一次相遇。实际上就是两指针都入环时,在环中相距L,F每一跳都将距离缩短1,直到追上S。
若D>0,S入环时走了D步,此时两指针相距L - (D mod L),第一次相遇的位置到C的距离为(D mod L)。这时候让F回到head,改为一次走一步,和S一起走。此时F到C的距离为D,S到C的距离为(D mod L),必然在C处再次相遇。
代码如下
public Node getCircle(Node head) {
if(head == null) return null;
Node fast = head, slow = head;
while(true) {
if(fast.next == null || fast.next.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
if(fast == slow) {
fast = head;
break;
}
}
while(fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
给两个单链表,判断是否相交,若相交,返回相交的第一个节点,否则返回null,要求空间复杂度O(1)。
用散列表可以轻松判定是否相交,但是额外空间复杂度O(n)。这里稍微动动脑子,可以实现空间复杂度O(1)。
两个可能有环的链表有三种情况:
1.两个都无环,如果两链表相交,尾部节点必然相等。
2.两个都有环,单链表最多有一个环,若相交,从一个链表的入环点出发,必然能到达另一个链表的入环点。
3.一个有环一个无环,因为两个单链表交点以后完全一样,所以不可能相交。
情况1:先找到两个尾部节点,如果相同即相交,找到两个链表长度差值,让长链表走到和短链表距离交点一样的位置,同时前进,相遇处即为交点。代码如下
public Node noCircle(Node head1, Node head2) {
int len1 = 1, len2 = 1;
Node cur1 = head1, cur2 = head2;
while(cur1.next != null) {
cur1 = cur1.next;
len1++;
}
while(cur2.next != null) {
cur2 = cur2.next;
len2++;
}
if(cur1 != cur2) {
return null;
}
cur1 = len1 > len2? head1: head2;
cur2 = cur1 == head1? head2: head1;
for(int i = 0; i < Math.abs(len1-len2); i++) {
cur1 = cur1.next;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
情况2:如果在同一点入环,和情况1一样,判定的时候把null改成入环点;在不同点入环,一个指针不动,另一个转一圈,期间相遇则相交,否则不相交。这里把交点规定为第一个参数的入环点。代码如下
public Node bothCircle(Node loop1, Node loop2) {
Node cur1 = loop1, cur2 = loop2;
if(loop1 == loop2) {//两链表在同一点入环,类似两个无环
int len1 = 1, len2 = 1;
while(cur1.next != loop1) {//只是null改成loop1
cur1 = cur1.next;
len1++;
}
while(cur2.next != loop1) {
cur2 = cur2.next;
len2++;
}
cur1 = len1 > len2? loop1: loop2;
cur2 = cur1 == loop1? loop2: loop1;
for(int i = 0; i < Math.abs(len1-len2); i++) {
cur1 = cur1.next;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
cur1 = cur1.next;
while(cur1 != loop1) {//如果自环就直接跳过了
if(cur1 == loop2) {
return loop1;//返回loop1,loop2都是一样的,这里规定成返回第一个参数的入环点
}
}
return null;//两个入环点不一样,入环点又无法相遇,不相交
}
主函数如下
public Node isIntersect(Node head1, Node head2) {
if(head1 == null || head2 == null) {//包含一个空链表就不存在相交
return null;
}
Node loop1 = getCircle(head1);
Node loop2 = getCircle(head2);
if(loop1 == null && loop2 == null) {
return noCircle(head1, head2);
}else if(loop1 == null && loop2 == null){
return bothCircle(loop1, loop2);
}else {
return null;
}
}