两个链表的第一个公共结点
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
数据范围: n≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)
例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:
可以看到它们的第一个公共结点的结点值为6,所以返回结点值为6的结点。
输入描述:
输入分为是3段,第一段是第一个链表的非公共部分,第二段是第二个链表的非公共部分,第三段是第一个链表和第二个链表的公共部分。 后台会将这3个参数组装为两个链表,并将这两个链表对应的头节点传入到函数FindFirstCommonNode里面,用户得到的输入只有pHead1和pHead2。
返回值描述:
返回传入的pHead1和pHead2的第一个公共结点,后台会打印以该节点为头节点的链表。
示例1
输入:
{1,2,3},{4,5},{6,7}
返回值:
{6,7}
说明:
第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分
这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的
示例2
输入:
{1},{2,3},{}
返回值:
{}
说明:
2个链表没有公共节点 ,返回null,后台打印{}
解法1:哈希表
遍历两个链表,同时将结点存储在哈希表中,并判断结点是否在哈希表中。
class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
// write code here
HashSet<ListNode> hashs = new HashSet<ListNode>();
while (pHead1 != null || pHead2 != null) {
if (pHead1 != null) {
if (hashs.Contains(pHead1))
return pHead1;
else {
hashs.Add(pHead1);
pHead1 = pHead1.next;
}
}
if (pHead2 != null) {
if (hashs.Contains(pHead2))
return pHead2;
else {
hashs.Add(pHead2);
pHead2 = pHead2.next;
}
}
}
return null;
}
解法2: 双指针
使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。
让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。
因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点。
下面看个动态图,可以更形象的表示这个过程~
以下是对使用两个指针分别遍历两个链表并在到达末尾后重新指向另一个链表头结点的方法中,指针一定会相遇且相遇位置为第一个公共结点的解释:
一、证明一定会相遇
- 假设两个链表无公共结点:
- 此时两个指针会同时到达各自链表的末尾(此时都为
null
),可以认为这也是一种相遇情况,只是相遇位置为null
,表明没有公共结点。
- 此时两个指针会同时到达各自链表的末尾(此时都为
- 假设两个链表有公共结点:
- 设链表
pHead1
的长度为m
,在公共结点前有a
个节点;链表pHead2
的长度为n
,在公共结点前有b
个节点,公共部分长度为c
。 - 指针
a
遍历完链表pHead1
后,走了m
步,再开始遍历链表pHead2
;指针b
遍历完链表pHead2
后,走了n
步,再开始遍历链表pHead1
。 - 两个指针总共走的步数是相同的,都是
m + n + c
(无论从哪个链表开始,最终都会走过两个链表的总长度加上公共部分的长度)。 - 由于步数相同且速度相同(每次都走一步),所以两个指针一定会在某个位置相遇。
- 设链表
二、相遇位置为第一个公共结点
- 当两个指针相遇时:
- 假设相遇时指针
a
在链表pHead2
中走了x
步,指针b
在链表pHead1
中走了y
步。 - 那么有等式:
m + x = n + y
(因为两个指针走的总步数相同(都是同时移动,且都是每次只移动一个结点))。 - 又因为
m - a = n - b
(公共长度都相等)。 - 联立两个等式可得:当
a
和b
相遇时,它们必定在第一个公共结点处。
- 假设相遇时指针
综上所述,使用这种方法,两个指针一定会相遇,且相遇位置为第一个公共结点。
class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
// write code here
ListNode node1 = pHead1;
ListNode node2 = pHead2;
while(node1 != node2)
{
node1 = (node1 == null) ? pHead2 : node1.next;
node2 = (node2 == null) ? pHead1 : node2.next;
}
return node2;
}
}
解法3:置为相同长度的起点
先统计两个链表的长度,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可。
class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
// write code here
if(pHead1 == null || pHead2 == null) return null;
int len1 = GetLength(pHead1), len2 = GetLength(pHead2);
//如果节点长度不一样,节点多的先走,直到他们的长度一样为止
while (len1 != len2) {
if (len1 > len2) {
//链表1长度大,先走
pHead1 = pHead1.next;
len1--;
} else {
//链表2长度大,先走
pHead2 = pHead2.next;
len2--;
}
}
//然后开始比较,如果他俩不相等就一直往下走
while (pHead1 != pHead2) {
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
//走到最后,最终会有两种可能,一种是phead1为空,
//也就是说他们俩不相交。还有一种可能就是phead1
//不为空,也就是说phead1就是他们的交点
return pHead1;
}
public int GetLength(ListNode node) {
int len = 0;
while (node != null) {
len++;
node = node.next;
}
return len;
}
}