题目
输入两个链表,找出它们的第一个公共结点。
(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,
保证传入数据是正确的)
两个链表的公共节点不会是链表的第一个结点,若是有公共结点,则公共结点之后的部分全都是公共的。
接下来看一下解题思路:
思路一:
首先想到的就是:遍历第一个链表上的每个节点,每遍历到一个节点的时候,在第二个链表上顺序遍历每个节点。若在第二个链表上有一个节点和第一个链表上的节点一样,说明两个链表在这个节点上重合,就是他们的公共节点
代码示例:
public ListNode FindFirstCommonNode1(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
for (ListNode p1 = pHead1; p1 != null; p1 = p1.next) {
for (ListNode p2 = pHead2; p2 != null; p2 = p2.next) {
if (p1 == p2) {
return p1;
}
}
}
return null;
}
总结
这里需要注意的是
- 这种方法时间复杂度比较大,如果第一个链表长为m,第二个链表长为n;那么时间复杂度就是O(mn);
思路二:
根据链表的定义,若是两个链表有公共节点,则这两个链表的公共节点之后一定都是共用的,所以可以定义两个
辅助空间(栈),分别用来存放两个链表,这样两个链表的尾结点就位于栈顶,接下来比较两个栈顶的节点是否相同,若相同就弹出栈,比较下一个,直到找到最后一个相同的结点。
代码示例:
public ListNode FindFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
//链表1入栈
while (pHead1 != null) {
stack1.push(pHead1);
pHead1 = pHead1.next;
}
//链表2入栈
while (pHead2 != null) {
stack2.push(pHead2);
pHead2 = pHead2.next;
}
//用于保存最后一个相同的节点
ListNode current = null;
//stack1.peek() 只是查看栈顶元素,并不删除
//stack1.pop() 删除并返回栈顶元素
while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.pop()) {
current = stack1.pop();
}
return current;
}
总结
这里需要注意的是
- 这种方法通过两个辅助空间换取时间,时间复杂度就是O(m + n);
思路三:
通过hashSet,先将第一个链表放到hashSet里面,再依次遍历第二个链表的每个节点, 若遇到相同的节点就返回。
代码示例:
public ListNode FindFirstCommonNode4(ListNode pHead1, ListNode pHead2){
if (pHead1 == null || pHead2 == null) {
return null;
}
Set<ListNode> nodeLists = new HashSet<>();
for (ListNode p = pHead1; p != null; p = p.next) {
nodeLists.add(p);
}
for (ListNode p = pHead2; p != null; p = p.next) {
if (nodeLists.contains(p)) {
return p;
}
}
return null;
}
总结
这里需要注意的是
- 这种方法和思路二一样,也是通过一个Set辅助,时间复杂度是O(m + n);
思路四
首先遍历两个链表得到他们的长度,算出长链表比短链表多几个结点, 在第二次遍历的时候,较长的链表先走若干步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是公共结点;
代码示例:
public ListNode FindFirstCommonNode3(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
int count1 = getCount(pHead1);
int count2 = getCount(pHead2);
if (count1 > count2) {
p1 = getCommon((count1 - count2), p1);
}
if (count1 < count2) {
p2 = getCommon((count2 - count1), p2);
}
return findCommon(p1, p2);
}
//寻找公共结点
private ListNode findCommon(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != null && p2 != null) {
if (p1 == p2) {
return p1;
}
p1 = p1.next;
p2 = p2.next;
}
return null;
}
//长的链表先走多余的几步
private ListNode getCommon(int count, ListNode pHead1) {
ListNode p = pHead1;
for (int i = 0; i < count; i++){
p = p.next;
}
return p;
}
//获取链表的结点个数
private int getCount(ListNode head) {
ListNode p = head;
int i = 0;
while (p != null) {
p = p.next;
i++;
}
return i;
}
总结
这种方法不需要借助辅助空间,而且时间复杂度为O(m+n)。
思路五(双指针法)
参考LeetCode交叉链表
首先创建两个头指针p1、 p2;分别指向链表headA和headB;然后依次向后遍历遍历,如果p1先到达链表尾部,则从headB链表头部重新开始遍历,类似的:如果p2先到达链表尾部,则从headA链表头部重新开始遍历,若在某一时刻 p1 和 p2 相遇,则 p1/p2 为相交结点.
为什么会这样呢?看一下官方给出的解释
想弄清楚为什么这样可行, 可以考虑以下两个链表:
A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于结点 9。
由于 B.length (=4) < A.length (=6),pB 比 pA 少经过 2 个结点,会先到达尾部。将 pB 重定向到 A 的头结点,pA 重定向到 B 的头结点后,pB 要比 pA 多走 2 个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当 pA/pB 到达链表结尾时,记录下链表 A/B 对应的元素。若最后元素不相同,则两个链表不相交。
看了官方的思路,确实不一般,很巧妙。
来源:力扣(LeetCode)
代码示例:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) {
return null;
}
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != p2) {
p1 = (p1 == null ? headB : p1.next);
p2 = (p2 == null ? headA : p2.next);
}
return p1;
}
总结
这种方法不需要借助辅助空间,而且
时间复杂度 : O(m+n)。
空间复杂度 : O(1)。
写完这个算法,看了一下评论区,发现了关于这个算法的一些浪漫 😮。
我走过你走过的路,你吹过我吹过的晚风,错的人迟早会走散,而对的人迟早会相逢!
想想这也算是学习的快乐吧!!!哈哈哈