题目
输入两个链表,找出它们的第一个公共节点。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
思路
因为单链表的每个节点都只有一个next域。那么在两个单链表存在公共节点的情况下,在两个链表的第一个公共节点之后,后面的所有节点就都是重合的,不可能再出现分叉。
- 利用两个辅助栈
当两个单链表存在公共节点的情况下,从它们的公共尾节点开始向前遍历,最后一个相同的节点,就是它们的第一个公共节点。
当两个单链表不存在公共节点的情况下,它们的尾节点就不是相同的。
==>定义两个辅助栈,分别遍历两个单链表,并将它们各自的节点压入栈中。最后,由两个堆栈的栈顶元素开始进行对比,来判断两个单链表是否有公共节点,以及有公共节点的情况下,对应的第一个公共节点。
时间复杂度O(m+n)
空间复杂度O(m+n)
- 双指针(一前一后)
1)先获取两个链表的长度l1、l2,计算出它们的长度的差值distance。
2)然后让较长的链表,先走出distance个节点(先向前遍历distance个节点)。
3)让指向两个链表的指针同步遍历,遍历时的条件为:
while(l1 != null && l2 != null && l1 != l2)
其中,l1 != null && l2 != null可以只写一个。因为它们的长度差已经先一步遍历了,从两个指针同时开始遍历的时候,两个链表所剩下的未遍历的节点数就是相等的。所以,只要一个链表遍历到尾节点,另一个链表必然也是遍历到了尾节点。
=>当退出循环的时候,对应着两种情况:
<1>l1 == null,即:两个链表遍历完成。
这说明,两个链表遍历完,也没有找到相同的节点,说明两个链表没有公共节点。直接return l1就可以(因为此时 l1 == null,正好对应两个链表没有尾节点时,应该返回的值)
<2>l1 == l2,即:找到了两个链表的第一个公共节点。
直接return l1即可。
时间复杂度O(m+n)
空间复杂度O(1)
- 双指针(浪漫相遇)
定义两个指针l1、l2,分别指向两个链表的头节点headA、headB,然后开始进行链表的 同步 遍历。
其中,l1在遍历完headA的链表之后,就接着遍历headB的链表;
l2在遍历完headB的链表之后,就接着遍历headA的遍历。
如果两个链表有公共节点,那么在遍历过程中,它们出现的第一个相同的节点,就是第一个公共节点;
如果两个链表没有公共节点,那么在遍历过程中,它们出现的第一个晓彤的节点,就是在l1、l2 同时完成了两条链表的遍历之后,所指向的相同的 == null的节点。
==>分析如下:
假设两链表存在公共节点,则公共节点所在的链表段为c,headA自己所特有的链表段为a,headB自己所特有的链表段为b。
则有:headA所在的链表为:a+c
headA所在的链表为:b+c。
那么,当l1遍历完headA之后,开始遍历headB;
l2遍历完headB之后,开始遍历headA;
两个指针,将在各自第二次遍历c段的时候相遇(因为此时,它们所遍历过的节点数相等):
a+c+b == b+c+a
while(listA != listB){
listA = (listA != null)? listA.next : headB ;
listB = (listB != null)? listB.next : headA ;
}
//需要注意的是:这里的循环内部的两个三元运算符用的很巧妙
//当headA和headB的长度不相等时,只有两个指针均把两条链表都遍历了一遍的情况下,才可能出现listA、listB均为null的情况,这时,他们会退出while循环---->正好,当两个链表没有公共节点。
//当headA和headB的长度相等时,两个指针都是 只遍历完一条链表都遍历时,就会出现listA、listB均为null的情况,---->正好也对应了两个链表没有公共节点的情况(因为两条链表的长度是相同的,如果两个链表有公共节点,在各自遍历第一条链表的时候,就会出现两个指针所指向的链表相同的情况,但是并没有出现这种情况,说明两个链表没有公共节点)
时间复杂度O(m+n)
空间复杂度O(1)
实现
- 利用两个辅助栈
因为这种方法对应的空间复杂度,不满足题意。所以,这里就省去了代码实现。 - 双指针(一前一后)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//先获取两个链表的长度
int lengthA = getLength(headA);
int lengthB = getLength(headB);
//定义两个指针,分别指向两个链表
ListNode listLong = headA;
ListNode listShort = headB;
//获取长度差
int distance = lengthA - lengthB;//假设A比B长
if(lengthA < lengthB){
//实际的长度,与假设的情况相反
listLong = headB;
listShort = headA;
distance = lengthB - lengthA;
}
//注意:如果distance==0的话,表明一样长,是不用先走的---->应该进不到for循环内,执行不了先走的语句才对
for(int i = 0;i < distance;i++){//这里用的很巧妙!!!!!!
listLong = listLong.next;
}
//开始正式,两个链表一起进行遍历操作
while(listLong != null && listLong != listShort){//不会有空指针异常,因为“&&”是短路与,当listLong为null的时候,就不会再去执行后面你的语句了
listShort = listShort.next;//往后遍历
listLong = listLong.next;//往后遍历
}
//退出while循环只有两种情况:
//1、两个链表都遍历完了,但是仍然没有找到第一个公共节点,=>说明两个链表没有交点
//此时,listLong、listShort的值,均为null(正好满足没有交点时 的返回值情况)
//2、在遍历的过程中,找到了第一个公共节点=>退出while循环
//此时,listLong、listShort指向的就是它们的第一个公共节点(正好满足它们有公共节点时 的返回值情况)
return listLong;
}
//获取链表长度的方法(从1开始计数)
public static int getLength(ListNode list){
ListNode cur = list;
int count = 0;
while(cur != null ){
count++;//计数
cur = cur.next;//完成链表的遍历
}
return count;
}
}
- 双指针(浪漫相遇)
//双指针(浪漫相遇)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//开始时,指针listA、listB分别指向headA、headB链表
ListNode listA = headA;
ListNode listB = headB;
while(listA != listB){
listA = (listA != null)? listA.next : headB ;
listB = (listB != null)? listB.next : headA ;
//需要注意的是:这里的循环内部的两个三元运算符用的很巧妙
//当headA和headB的长度不相等时,只有两个指针均把两条链表都遍历了一遍的情况下,才可能出现listA、listB均为null的情况,这时,他们会退出while循环---->正好,当两个链表没有公共节点。
//当headA和headB的长度相等时,两个指针都是 只遍历完一条链表都遍历时,就会出现listA、listB均为null的情况,---->正好也对应了两个链表没有公共节点的情况(因为两条链表的长度是相同的,如果两个链表有公共节点,在各自遍历第一条链表的时候,就会出现两个指针所指向的链表相同的情况,但是并没有出现这种情况,说明两个链表没有公共节点)
}
return listA;
}
}
不断地往前走,不断地往前走,等有一天,你会突然发现:原来已经离起点那么远了!