题目:
Write a program to find the node at which the intersection of two singly linked lists begins.
For example, the following two linked lists:
begin to intersect at node c1.
Example 1:
Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 Output: Reference of the node with value = 8 Input Explanation: The intersected node's value is 8 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [4,1,8,4,5]. From the head of B, it reads as [5,0,1,8,4,5]. There are 2 nodes before the intersected node in A; There are 3 nodes before the intersected node in B.
Example 2:
Input: intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 Output: Reference of the node with value = 2 Input Explanation: The intersected node's value is 2 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [0,9,1,2,4]. From the head of B, it reads as [3,2,4]. There are 3 nodes before the intersected node in A; There are 1 node before the intersected node in B.
Example 3:
Input: intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 Output: null Input Explanation: From the head of A, it reads as [2,6,4]. From the head of B, it reads as [1,5]. Since the two lists do not intersect, intersectVal must be 0, while skipA and skipB can be arbitrary values. Explanation: The two lists do not intersect, so return null.
Notes:
- If the two linked lists have no intersection at all, return
null
. - The linked lists must retain their original structure after the function returns.
- You may assume there are no cycles anywhere in the entire linked structure.
- Your code should preferably run in O(n) time and use only O(1) memory.
这道题的解法非常巧妙,由于它是要求两个链表在尾部的交集,而这两个链表在相交点之前的长度可能并不相同,因此无法直接从前往后比较,需要将两个链表的尾部进行对齐,使指向两个链表的指针最后剩余的节点数相同。要使两个指针剩余的节点数相同,首先比较直观的想法就是先遍历两个链表,求出两个链表的长度,并让指向长的链表的指针先走多出来的节点数,接下来就可以对两个链表同步比较了,但是这样比较耗时,代码写起来也很繁琐。
另外一种精妙的解法就是连接两个链表,相当于构造虚拟链表A'和B',A'=A+B,B'=B+A,最后A'和B'的长度都是一样的,因此可以同步比较。如何构造这两个虚拟链表呢,那就是当一个链表上的指针走完以后,把它接到另一个链表上。
第一种方法代码如下,时间52ms,86.95%,空间17M,16.69%
/*
* @lc app=leetcode id=160 lang=cpp
*
* [160] Intersection of Two Linked Lists
*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) {
return NULL;
}
ListNode* p1 = headA;
ListNode* p2 = headB;
int len1 = 0;
int len2 = 0;
while (p1) {
len1++;
p1 = p1->next;
}
while (p2) {
len2++;
p2 = p2->next;
}
p1 = headA;
p2 = headB;
while (len1 > len2) {
p1 = p1->next;
len1--;
}
while (len1 < len2) {
p2 = p2->next;
len2--;
}
while (p1 != p2 && p1 && p2) {
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
};
第二种方法代码如下,时间52ms,86.95%,空间16.9M,21.59%
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) {
return NULL;
}
ListNode* p1 = headA;
ListNode* p2 = headB;
while (p1 != p2) {
p1 = p1->next;
p2 = p2->next;
if (p1 == p2) {
return p1;
}
if (!p1) {
p1 = headB;
}
if (!p2) {
p2 = headA;
}
}
return p1;
}
};
这道题一个比较复杂的地方其实是它的返回条件,两种解法的返回条件都需要好好分析一下:
第一种方法在while循环中判断两个指针是否相等,以及任意一个指针是否为NULL(其实说是任一指针为空,其实两个最后都是同步的,不会存在一个为空一个不为空的情况),如果没有相等就继续比较直到走到NULL,相等就直接返回,返回谁都一样。
第二种方法就比较复杂了,由于在while循环中只判断两个指针是否相等,于是需要在循环里额外比较两个指针是否相等,如果相等就直接返回(包括最后集体指向NULL表示不相交),我觉得应该不会跳到最后的return吧?
这道题的discussion里也干货多多,这个里面有很详细的讲解:https://leetcode.com/problems/intersection-of-two-linked-lists/discuss/49789/My-accepted-simple-and-shortest-C%2B%2B-code-with-comments-explaining-the-algorithm.-Any-comments-or-improvements;这个里面有可视化的题解:https://leetcode.com/problems/intersection-of-two-linked-lists/discuss/49785/Java-solution-without-knowing-the-difference-in-len!
还有一个非常奇妙的通过反转链表和异或操作来实现的,真的很厉害能想到这种方法,代码就不写了,放个链接在这里留个思路:https://leetcode.com/problems/intersection-of-two-linked-lists/discuss/49899/C%2B%2B-solution-using-XOR-trick
2022.11.7
就,还是没想到做法,哎。
第一个做法,先求链表长度,让长的链表先走二者的diff,然后两个一起移动,就可以直接对比两个是否相等了。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
int len1 = 1;
int len2 = 1;
while (p1.next != null) {
p1 = p1.next;
len1++;
}
while (p2.next != null) {
p2 = p2.next;
len2++;
}
p1 = headA;
p2 = headB;
int diff = Math.abs(len2 - len1);
if (len1 > len2) {
for (int i = 0; i < diff; i++) {
p1 = p1.next;
}
} else {
for (int i = 0; i < diff; i++) {
p2 = p2.next;
}
}
while (p1 != null || p2 != null) {
if (p1 == p2) {
return p1;
}
p1 = p1.next;
p2 = p2.next;
}
return null;
}
}
第二种做法,当遍历到一个链表结尾的时候,下一个让它接着遍历另一个链表,相当于变相让这俩链表长度相等,就可以直接比较了。刚开始写代码的时候理解有点问题,用p1.next接到了headB上,那确实就相当于把两个链表前后相连造了个环出来,最后就光荣TLE了。其实应该直接判断p1/p2是否为null,并update p1/p2。而且不应该在while里面判断p1 == p2外面return null,毕竟while的循环条件是while (p1 != p2)……fine, I'm so stupid.
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
while (p1 != p2) {
if (p1 == null) {
p1 = headB;
} else {
p1 = p1.next;
}
if (p2 == null) {
p2 = headA;
} else {
p2 = p2.next;
}
}
return p1;
}
}