1 四种方法解决两个链表第一个公共子节点
两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。
将常用的数据结构和算法常用思想都想一遍;常用的数据结构有数组,链表,队列,栈,Hash,集合,数,堆。常用的算法思想有查找,排序,双指针,递归,迭代,分治,贪心,回溯和动态规划等
首先想到的方法是蛮力法,类似于冒泡排序的方式,将第一个链表中的每一个结点依次与第二个链表进行比较,当出现相等的结点指针时,即为相交结点。虽然简单,但是时间复杂度高,排除!
再看Hash,先将第一个链表元素全部存到 Map 里,然后一边遍历第二个链表,一边检测当前元素是否在 Hash 中,如果两个链表有交点,那就找到了。既然 Hash 可以,那么集合也能解决。还可以使用栈,将两个链表分别压到两个栈里,之后一边同时出栈,一边比较出栈元素是否一致,如果一致则说明存在相交。然后继续找,最晚出栈的那组一致的结点就是要找的位置。
综上我们得到了四种方法。
1.1 哈希和集合
先将一个链表元素全部存到Map里,然后一边遍历第二个链表,一边检测Hash中是否存在当前结点,如果有交点,那么一定能检测出来。
/**
* 哈希和集合
* @param headA
* @param headB
* @return
*/
public Node findFirstCommonNodeBySet(Node headA,Node headB){
HashSet<Node> set = new HashSet<>();
while(headA != null){
set.add(headA);
headA = headA.next;
}
while(headB != null){
if (set.contains(headB)){
return headB;
}
headB = headB.next;
}
return null;
}
1.2 使用栈
这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,一直找到最晚出栈的那一组。这种方式需要两个O(n)的空间。
/**
* 使用栈
* @param headA
* @param headB
* @return
*/
public Node findFirstCommonNodeByStack(Node headA,Node headB){
Stack<Node> stackA = new Stack<>();
Stack<Node> stackB = new Stack<>();
while(headA != null){
stackA.push(headA);
headA = headA.next;
}
while(headB != null){
stackB.push(headB);
headB = headB.next;
}
Node preNode = null;
while(stackA.size() > 0 && stackB.size() > 0){
if (stackA.peek() == stackB.peek()){
preNode = stackA.pop();
stackB.pop();
}else {
break;
}
}
return preNode;
}
1.3 拼接两个字符串
先看下面两个链表A和B:
A : 0-1-2-3-4-5
B : a-b-4-5
如果分别拼接成AB和BA会怎样呢?
AB : 0-1-2-3-4-5-a-b-4-5
AB : a-b-4-5-0-1-2-3-4-5
我们发现从最后的4开始两个链表是一样的了,自然4就是我们要找的结点,所以可以通过拼接的方式来寻找交点。这么做的原理是什么?从几何的角度来分析,我们假定A和B有相交的位置,以交点为中心,可以将两个链表分别分为left_a和right_a, left_b和right_b这样四个部分,并且right_a和right_b是一样的,这时候我们拼接AB和BA就是这样的结构:
这里还可以进一步优化,建立新的链表太浪费空间,我们只要在每个链表访问完了之后,调整到下一链表的表头继续遍历就行了。
public Node findFirstCommonNode(Node pHead1,Node pHead2){
if (pHead1 == null || pHead2 == null){
return null;
}
Node p1 = pHead1;
Node p2 = pHead2;
while(p1 != p2){
p1 = p1.next;
p2 = p2.next;
if (p1 != p2){
//一个链表访问完了就跳转到另一个链表继续访问
if (p1 == null){
p1 = pHead2;
}
if (p2 == null){
p2 = pHead1;
}
}
}
return p1;
}
1.4 差和双指针
还可以使用差和双指针来解决问题。例如公共子结点一定存在第一轮遍历,假设La长度为L1,Lb长度为L2.则| L2 - L1 | 就是两个的差值。第二轮遍历,长的先走 | L2 - L1 | ,然后两个链表同时向前走,结点一样的时候就是公共结点了。
public Node findFirstCommonNode1(Node pHead1,Node pHead2){
if (pHead1 == null || pHead2 == null){
return null;
}
Node current1 = pHead1;
Node current2 = pHead2;
int l1 = 0,l2 = 0;
//分别统计两个链表的长度
while(current1 != null){
current1 = current1.next;
l1++;
}
while(current2 != null){
current2 = current2.next;
l2++;
}
current1 = pHead1;
current2 = pHead2;
int sub = l1 > l2 ? l1-l2 : l2 - l1;
//长的先走 sub 步
if (l1 > l2){
int a = 0;
while (a < sub){
current1 = current1.next;
a++;
}
}
if (l2 > l1){
int a = 0;
while (a < sub){
current2 = current2.next;
a++;
}
}
//同时遍历两个链表
while(current2 != current1){
current2 = current2.next;
current1 = current1.next;
}
return current1;
}