题目
写一个程序找出两个单链表的交叉节点。
- 算法要求:
- 如果两个链表没有交叉点,就返回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