两个单链表相交的一系列问题(成环,不成环)

813 阅读5分钟

【题目】 在本题中,单链表可能有环,也可能无环。给定两个
单链表的头节点 head1和head2,这两个链表可能相交,也可能
不相交。请实现一个函数, 如果两个链表相交,请返回相交的
第一个节点;如果不相交,返回null 即可。 要求:如果链表1
的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外

空间复杂度请达到O(1)。

我们将问题分为四个部分来讨论: 1.如何判断链表有无环 2.两个有环链表相交 3.两个无环链表相交 4.一个有环,一个无环的情况的不会相交的,这是有单链表的结构决定的

方法1:使用set

1.判断有无环--将节点加入到set中,如果出现一个节点之前已经存在了的,那个就是第一个相交的节点
否则如果一直到空都没有相同节点,就是无环了(注意:两个节点相等就是内存地址相等)
2.两个无环链表,其中一个链表加入到set中,另一个链表也一次加入到set中,如果出现一个已经存在了,
那个就是第一个相交的节点了,如果一直没有相同的节点,也就是没有相交了。。。。

这种方法有点水,我就不讲了,而且这是达不到题目的空间复杂度要求的

方法2:

* 1.判断链表有环无环

* 三个以上节点的链表才可能构成环
* 快指针走一步,慢指针走两步
* 只要都不为空,两个指针相交的时候,快指针回到头指针位置

* 快指针和慢指针都走一步,两指针再次相交的时候返回该节点就是有环链表第一个相交的节点

	public static Node getLoopNode(Node head){
		if(head==null || head.next==null || head.next.next==null){
			return null;
		}
		Node slow = head.next;
		Node fast = head.next.next;
		while(slow!=fast){
			if(fast.next == null || fast.next.next == null){
				return null;
			}
			slow = slow.next;
			fast = fast.next.next;
		}
		//前面没有返回来到这里就是有相交了
		fast = head;
		while(slow != fast){
			fast = fast.next;
			slow = slow.next;
		}
		return slow;
	}

* 2.无环链表相交

* 获取两个链表的len1,len2,end1,end2
* 如果两个无环链表相交,那么他们的尾节点一定是同一个(每一个节点只有一个后继节点决定的)
* 虽然尾节点是同一个了,但题目要求的是第一个相交的节点...下面
* 长的链表先走,走到和短的链表一样长的时候,两个链表一起走

* 当两个链表相等的时候,就是第一个相交的节点

	public static Node noLoop(Node head1,Node head2){
		int len1 = 1,len2 = 1;
		Node end1 = head1;
		Node end2 = head2;
		while(end1.next!=null){
			len1++;
			end1 = end1.next;
		}
		while(end2.next!=null){
			len2++;
			end2 = end2.next;
		}
		if(end1!=end2){//如果无环链表相交,最后一个节点一定是同一个节点
			return null;
		}
		end1 = head1;//复用一下变量而已
		end2 = head2;
		while(len1!=len2){
			if(len1>len2){
				len1--;
				end1 = end1.next;
			}else{
				len2--;
				end2 = end2.next;
			}
		}
		//现在两个链表一起走
		while(end1!=end2){
			end1 = end1.next;
			end2 = end2.next;
		}
		return end1;
	}

* 3.有环链表相交

* 两个有环链表有三种情况
* 情况1:两个成环链表各自成环,不相交
* 情况2:两个链表先相交,后一起成环,这种可以用noLoop的方式处理,
* 一样的,但是不能直接调用,否则就屎循环了
* 情况3:两个两个链表各自走进同一个环中
*
* 获取end1,end2
* 如果end1==end2,情况2
* 如果end1!=end2,情况2或3
* 这个时候end1一直往前走,如果回到他的入环节点前遇到了end2
* 就是情况3,也就是相交,否则就是情况1,不相交
* 因为是有环的,所以end1就是loop1,end2就是loop2

* 过程结束!

	public static Node bothLoop(Node head1,Node head2,Node loop1,Node loop2){
		Node cur1 = null;
		Node cur2 = null;
		if (loop1 == loop2) {
			cur1 = head1;
			cur2 = head2;
			int n = 0;
			while (cur1 != loop1) {
				n++;
				cur1 = cur1.next;
			}
			while (cur2 != loop2) {
				n--;
				cur2 = cur2.next;
			}
			//如果n大于0,就是链表1比较长,则设置cur1指向链表1的头节点(否则亦然)
			cur1 = n > 0 ? head1 : head2;
			//设置cur2指向短链表的头节点
			cur2 = cur1 == head1 ? head2 : head1;
			n = Math.abs(n);
			//长链表走到和短链表相同的长度的位置
			while (n-- > 0) {
				cur1 = cur1.next;
			}
			while (cur1 != cur2) {
				cur1 = cur1.next;
				cur2 = cur2.next;
			}
			return cur1;
		} else {
			cur1 = loop1.next;
			while (cur1 != loop1) {
				if (cur1 == loop2) {
					return loop1;
				}
				cur1 = cur1.next;
			}
			return null;
		}
	}

最后是调用过程和测试用例:

	public static class Node {
		public int value;
		public Node next;

		public Node(int value) {
			this.value = value;
		}
	}

	public static Node getIntersectNode(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node loop1 = getLoopNode(head1);
		Node loop2 = getLoopNode(head2);
		//要么两个链表都无环,要么都有环,否则没有相交的可能性
		if (loop1 == null && loop2 == null) {
			return noLoop(head1, head2);
		}
		if (loop1 != null && loop2 != null) {
			return bothLoop(head1, head2,loop1,loop2);
		}
		return null;
	}

	public static void main(String[] args) {
		// 1->2->3->4->5->6->7->null
		Node head1 = new Node(1);
		head1.next = new Node(2);
		head1.next.next = new Node(3);
		head1.next.next.next = new Node(4);
		head1.next.next.next.next = new Node(5);
		head1.next.next.next.next.next = new Node(6);
		head1.next.next.next.next.next.next = new Node(7);

		// 0->9->8->6->7->null
		Node head2 = new Node(0);
		head2.next = new Node(9);
		head2.next.next = new Node(8);
		head2.next.next.next = head1.next.next.next.next.next; // 8->6
		System.out.println(getIntersectNode(head1, head2).value);

		// 1->2->3->4->5->6->7->4...
		head1 = new Node(1);
		head1.next = new Node(2);
		head1.next.next = new Node(3);
		head1.next.next.next = new Node(4);
		head1.next.next.next.next = new Node(5);
		head1.next.next.next.next.next = new Node(6);
		head1.next.next.next.next.next.next = new Node(7);
		head1.next.next.next.next.next.next = head1.next.next.next; // 7->4

		// 0->9->8->2...
		head2 = new Node(0);
		head2.next = new Node(9);
		head2.next.next = new Node(8);
		head2.next.next.next = head1.next; // 8->2
		System.out.println(getIntersectNode(head1, head2).value);

		// 0->9->8->6->4->5->6..
		head2 = new Node(0);
		head2.next = new Node(9);
		head2.next.next = new Node(8);
		head2.next.next.next = head1.next.next.next.next.next; // 8->6
		System.out.println(getIntersectNode(head1, head2).value);
		
		//答案:6 2 4
	}