9、相交链表
题目
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
-
listA
中节点数目为m
-
listB
中节点数目为n
-
0 <= m, n <= 3 * 104
-
1 <= Node.val <= 105
-
0 <= skipA <= m
-
0 <= skipB <= n
-
如果
listA
和listB
没有交点,intersectVal
为0
-
如果
listA
和listB
有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]
- skipA,skipB指的是A,B链表相交前的节点数量
- intersectVal指的是A,B链表相交节点的值
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
9.0、暴力解法
先确定A中一点,然后遍历B节点,当A,B节点相同时就返回该结点
然后确定为A中的下一个节点,继续重复遍历B中节点
找不到相同的,就返回null
- 时间复杂度:O(m n)m为A链表长度,n为B链表的长度
- 空间复杂度:O(1)
9.1、暴力解法优化(哈希)
利用哈希集合来储存一个链表的所有节点,然后遍历另一个链表来看看集合里存不存在该节点
ListNode* getIntersectionNode1(ListNode* headA, ListNode* headB)
{
if (!headA || !headB)return NULL; //有一个头节点为空,直接返回空
unordered_set<ListNode*> data;
ListNode* aNode = headA, * bNode = headB;
while (aNode) //将A链表中的节点存入哈希集合中
{
data.insert(aNode);
aNode = aNode->next;
}
while (bNode)
{
if (data.count(bNode))
return bNode;
bNode = bNode->next;
}
return NULL;
}
- 时间复杂度:O(m + n)
- 空间复杂度:O(n)
9.2、一般解法(我是这么写的)
对于链表:(n表示为不存在)
A:【5,3,7,1,8,2,0,9,4】
B:【n,n,n,4,6,2,0,9,4】
假设A,B在2处相交
指针分别指向5,4
设想一下,我们让A的指针先走3步也就是5–>3–>7–>1
现在指针分别指向1,4
两个指针同时向前移动,是不是正好相交于2这个点
下面的代码为了把功能分开写,所以提取出了函数,其实也可以写在一起。
int countSize(ListNode* head)
{
int size = 0;
while (head)
{
size++;
head = head->next;
}
return size;
}
ListNode* moveHead(ListNode* head, int num)
{
for (int i = 0; i < num; i++)
head = head->next;
return head;
}
ListNode* getIntersectionNode2(ListNode* headA, ListNode* headB)
{
if (!headA || !headB)return NULL; //有一个头节点为空,直接返回空
int listASize = countSize(headA), listBsize = countSize(headB);//记录两个链表的大小
if (listASize > listBsize) //让两个head在同一起跑线
headA = moveHead(headA, listASize - listBsize);
else if(listBsize>listASize)
headB = moveHead(headB, listBsize - listASize);
while (headA) //开始一一比较
{
if (headA == headB)
return headA;
headA = headA->next;
headB = headB->next;
}
return NULL;
}
- 时间复杂度:O(max(m,n))m为A链表长度,n为B链表的长度
- 空间复杂度:O(1)
9.3、不容易想到的解法(我也是看别人的)
对于链表:(n表示为不存在)
A:【5,3,7,1,8,2,0,9,4】
B:【n,n,n,4,6,2,0,9,4】
假设A,B在2处相交
在上面那种方法中,先让A先走3步,使两个指针可以同步向前
现在,把A,B想象为:(B拼接在A后面,A拼接在B后面)
A:【5,3,7,1,8,2,0,9,4,4,6,2,0,9,4】
B:【n,n,n,4,6,2,0,9,4,5,3,7,1,8,2,0,9,4】
删除n
A:【5,3,7,1,8,2,0,9,4 || 4,6,2,0,9,4】
B:【4,6,2,0,9,4 || 5,3,7,1,8,2,0,9,4】
可以发现,两个指针同步向前,到最后是正好同时遍历到2
ListNode* getIntersectionNode3(ListNode* headA, ListNode* headB)
{
if (!headA || !headB)return NULL; //有一个头节点为空,直接返回空
ListNode* aNode = headA, * bNode = headB;
while (aNode != bNode) //都为空或者相交节点时退出循环
{
aNode = aNode == NULL ? headB : aNode->next;
bNode = bNode == NULL ? headA : bNode->next;
}
return aNode;
}
- 时间复杂度:O(max(m,n))m为A链表长度,n为B链表的长度
- 空间复杂度:O(1)
性能和上面一样,但是思想值得学习。