判断单链表是否存在环,两个单链表是否相交

给出一个单链表,节点结构如下

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;
	}
}

猜你喜欢

转载自blog.csdn.net/Daniel_2046/article/details/81043024