算法题008 -- [写一个程序找出两个单链表的交叉节点] by java

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cjh_android/article/details/83994215

题目

写一个程序找出两个单链表的交叉节点。

    算法要求:
  • 如果两个链表没有交叉点,就返回null
  • 这两个链表必须在方法结束后保持他们原来的数据结构
  • 你可以认定在链表的结构不存在环
  • 时间复杂度O(n), 空间复杂度O(1)
举例:
 链表 A: a1 → a2 → a3 → c1 → c2 → c3
 链表 B:      b1 → b2 → c1 → c2 → c3
 那么 A B 就是两个有交叉节点的链表,他们在 C1 处开始重叠

思路

    在上述要求下:
  • 数据结构不变:我的理解是不建议使用链表反转,否则在最后返回时,还要见缝插针的再反转回来;
  • 时间复杂度 O(n),限制了对该题目的暴力解法 -- 双循环;
  • 空间复杂度O(1),表明在代码中不能因为n的增长,出现相应的内存对象的增长;所以其实就是对链表各种操作,而不能引入会导致内存增长的对象。

其实不管是哪一种方法,在空间复杂度只有O(1)的情况下,都需要让两个链表从同一坐标处开始遍历,比如例子中链表A从a2,链表B从b2,这样他们就可以在一个循环内同时到达C1。

代码

在代码中,只会讲解方法一的思路,因为方法一的思路简单些,而方法二会在后面推导出来,与其说是写代码,不如说是做数学题~

package algorithm8;

import algorithm6.ListNode;

public class Algorithm8 {
	
	public static void main(String[] args) {
		ListNode intersectionNode = getIntersectionNode();
		ListNode listNodeA = getListNodeA(intersectionNode);
		ListNode listNodeB = getListNodeB(intersectionNode);
		
		ListNode realIntersectionNode = getIntersectionNode2(listNodeA, listNodeB);
		System.out.println((realIntersectionNode == null)? "null" : realIntersectionNode.toString());
	}
	
	/**方法一:
	 * 思路:在上述的思路分析中,已经提到过,其实就是让两个链表处在相同的坐标位置开始遍历
	 * 		相同坐标是为了两个链表可以同时到达交叉点,如果有交叉点...
	 * 		在不知道长度的链表 A 和 B,需要先将较长链表的指针前进多出来的长度
	 * 		于是分别计算出 A、B 长度后,如果 A 比 B多了n个节点,那么A的指针前进n个节点后
	 * 		此时 A、B就可以一起进入循环,找出交叉点了
	 * 优点:思路清晰
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static ListNode getIntersectionNode1(ListNode a, ListNode b) {
		int lengthA = getLength(a);
		int lengthB = getLength(b);
		int len = lengthA - lengthB;
		ListNode curA = len >= 0 ? a : b;
		ListNode curB = len < 0 ? a : b;
		len = Math.abs(len);
		while(len > 0) {
			curA = curA.next;
			len --;
		}
		while(curA != null && curB != null && curA != curB) {
			curA = curA.next;
			curB = curB.next;
		}
		return curA;
	}
	
	/**方法二:
	 * 思路: 见下面推导
	 * @param a
	 * @param b
	 * @return
	 */
	public static ListNode getIntersectionNode2(ListNode a, ListNode b) {
		ListNode curA = a;
		ListNode curB = b;
		while(curA != curB) {
			curA = (curA==null)? b : curA.next;
			curB = (curB == null) ? a : curB.next;
		}
		return curA;
	}
	
	public static int getLength(ListNode listNode) {
		int length = 0;
		ListNode temp = listNode;
		while(temp != null) {
			temp = temp.next;
			length++;
		}
		return length;
	}
	
	public static ListNode getListNodeA(ListNode intersectionNode) {
		ListNode listNode1 = new ListNode(1);
		ListNode listNode3 = new ListNode(3);
		ListNode listNode5 = new ListNode(5);
		ListNode listNode7 = new ListNode(7);
		listNode1.next = listNode3;
		listNode3.next = listNode5;
		listNode5.next = listNode7;
		listNode7.next = intersectionNode;
		return listNode1;
	}
	
	public static ListNode getListNodeB(ListNode intersectionNode) {
		ListNode listNode2 = new ListNode(2);
		ListNode listNode4 = new ListNode(4);
		ListNode listNode6 = new ListNode(6);
		ListNode listNode8 = new ListNode(8);
		listNode2.next = listNode4;
		listNode4.next = listNode6;
		listNode6.next = listNode8;
		listNode8.next = intersectionNode;
		return listNode2;
	}
	
	public static ListNode getIntersectionNode() {
		ListNode listNode10 = new ListNode(10);
		ListNode listNode11 = new ListNode(11);
		ListNode listNode12 = new ListNode(12);
		ListNode listNode13 = new ListNode(13);
		listNode10.next = listNode11;
		listNode11.next = listNode12;
		listNode12.next = listNode13;
		return listNode10;
	}
}
方法二的推导:

假设有链表 A,链表B,他们有交叉点,并且 A.length > B.length,如下图:

在这里插入图片描述
相关数据都在上图中标注好了,此时已知的条件:

  • hA -> E = M + N + J
  • hB -> E = N + J
设有两个指针 P1,P2,分别从 hB、hA遍历链表前进,当P1 从 hB 前进至 E时:

在这里插入图片描述

如上图,此时:
  • 由于P1到了终点E,前进了的距离其实就是 hB —> E 距离: N+J
  • 由P1前进的距离,知道P2前进了: hAP2 = N+J, 那么 P2E = hAE - hAP2 = M + N + J - (N+J) = M
此时如果P2继续前进,而将到达终点的P1指向hA位置,当P2前进M距离到达终点时,P1也在链表A中前进了M距离,到达位置K,此时指针P1的位置与hB正好对应,于是就找到了前进循环中链表A的入口点;将指针P2指向hB, P1、P2一起前进,若有交叉点C,那么就可以找出来了。

猜你喜欢

转载自blog.csdn.net/cjh_android/article/details/83994215
今日推荐