0. 相关文章
1. 题目
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
数据范围: n \le 1000n≤1000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:
可以看到它们的第一个公共结点的结点值为6,所以返回结点值为6的结点。
输入描述:
输入分为是3段,第一段是第一个链表的非公共部分,第二段是第二个链表的非公共部分,第三段是第一个链表和第二个链表的公共部分。 后台会将这3个参数组装为两个链表,并将这两个链表对应的头节点传入到函数FindFirstCommonNode里面,用户得到的输入只有pHead1和pHead2。
返回值描述:
返回传入的pHead1和pHead2的第一个公共结点,后台会打印以该节点为头节点的链表。
2. 题解
此题有多种解法,由于暴力解法时间复杂度太高,所以此处我们不考虑。
2.1 题解1:使用Hash
- 我们先将链表1的所有节点都存储到一个HashSet中。
- 遍历链表2的节点,找到第一个在HashSet中存在的节点,即为两个链表的第一个公共节点。
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
return findByHash(pHead1, pHead2);
}
private ListNode findByHash(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
// 1. 将pHead1的所有节点添加进HhashSet中
Set<ListNode> set = new HashSet<>();
ListNode tempNode = pHead1;
while (tempNode != null) {
set.add(tempNode);
tempNode = tempNode.next;
}
// 2. 遍历pHead2,找到第一个在Set集合中的节点,即为第一个公共节点
tempNode = pHead2;
while (tempNode != null) {
if (set.contains(tempNode)) {
return tempNode;
}
tempNode = tempNode.next;
}
return null;
}
}
2.2 题解2:使用栈
- 将两个链表分别压入两个栈中
- 比较栈顶元素,如果相同,即为该节点的公共节点。
- 因此我们将相同的节点同时出栈,找到的最后一个相同的节点,即为第一个公共子节点。
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
return findByStack(pHead1, pHead2);
}
private ListNode findByStack(ListNode pHead1, ListNode pHead2) {
// 1. 将两个链表存入两个栈中
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
ListNode tempNode1 = pHead1;
while (tempNode1 != null) {
stack1.push(tempNode1);
tempNode1 = tempNode1.next;
}
ListNode tempNode2 = pHead2;
while (tempNode2 != null) {
stack2.push(tempNode2);
tempNode2 = tempNode2.next;
}
// 2. 同时出栈,找到第最后一组相同节点,即为第一个公共节点
ListNode prefNode = null;
while (!stack1.isEmpty() && !stack2.isEmpty()) {
if (stack1.peek() != stack2.peek()) {
return prefNode;
}
prefNode = stack1.peek();
stack1.pop();
stack2.pop();
}
return prefNode;
}
}
2.3 题解3:通过拼接序列
如果对此题没有思路时,我们肯定会产生一个想法:如果这两个链表一样长就好了。就如下图所示,如果两个链表一样长,我们只需要同时遍历"链表1"和"链表2",找到的第一个相同的节点就是答案。
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
while (pHead1 != null && pHead2 != null) {
if (pHead1 == pHead2) {
return pHead1;
}
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
}
但是现在的问题是两个链表的长度不同,如题干中所示链表
那么如果将两个链表的长度变得相同呢,很简单:我们只需要将链表分别拼接起来即可,如下图所示
此时我们使用上面的方法,去遍历两个链表,就可以在倒数第二个节点,找到他们的第一个公共节点(6)
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
return findFirstCommonNodeByCombine(pHead1, pHead2);
}
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode tempNode1 = pHead1;
ListNode tempNode2 = pHead2;
// 如同上图所示,拼接后两个链表长度相同,同步遍历时肯定同时走到尾节点
// 如果此时都没有因找到公共节点而退出,则表明无公共节点
while (tempNode1 != null || tempNode2 != null) {
// 如果一个链表为null,则表明该链表已经走到链表尾部
// 此时将另一个链表拼接上去继续遍历
if (tempNode1 == null) {
tempNode1 = pHead2;
}
if (tempNode2 == null) {
tempNode2 = pHead1;
}
// 如果节点相同直接返回,否则同时遍历两条链表下一个节点。
if (tempNode1 == tempNode2) {
return tempNode1;
}
tempNode1 = tempNode1.next;
tempNode2 = tempNode2.next;
}
return null;
}
}
2.4 题解4:通过差辅助查找
在题解三中,我们可以知道,只需要让两个链表的长度相等,那么我们便可以很轻松的找到他们的第一个公共节点。
因此:我们可以先求出两个链表的长度,长链表先走n(长度差)步,剩余的链表长度就相等了。
以此题为栗子:
- 遍历链表,分别求出两条链表的长度(链表1=5;链表2=4)
- 长度较长的链表(链表1)先向后遍历1步(5-4=1)
- 此时的链表为(链表1=2,3,6,7;链表2=4;5;6;7)
- 此时链表1和链表2长度相同,只需要同步遍历便能获取结果。