五种方法解决两个链表第一个公共子节点
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五种方法解决两个链表第一个公共子节点相关的知识,希望对你有一定的参考价值。
这是一道经典的链表问题先看一下题目。
输入两个链表,找出它们的第一个公共节点。
例如下面的两个链表:
两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。
1.没有思路时该怎么解题 这种问题该怎么入手呢?如果一时想不到该怎么办呢?其实这时候我们可以将常用数据结构和常用算法都想一遍,看看哪些能解决问题。
常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。
我们先在脑子里快速过一下谁有可能解决问题。首先想到的是蛮力法,类似于冒泡排序的方式,将第一个链表中的每一个结点依次与第二个链表的进行比较,当出现相等的结点指针时,即为相交结点,但是这种方法时间复杂度高,而且有可能只是部分匹配上,所以还有要处理复杂的情况。排除!
其次Hash呢?模模糊糊感觉行的,OK。
之后是集合呢?和Hash一样用,目测也能解决,OK。
队列和栈呢?貌似队列没啥用,但是栈能解决问题,于是就有了第三种方法。
其他的几种结构或者算法呢?貌似都不太好用。这时候我们可以直接和面试官说,应该可以用HashMap做,另外集合和栈应该也能解决问题。面试官很明显就问了,怎么解决?
那这时候你可以继续考虑HashMap、集合和栈具体应该怎么解决,假如错了呢?比如你说队列也行,但是后面发现根本解决不了,这时候直接对面试官说“队列不行,我想想其他方法”,一般对方就不会再细究了。
算法面试本身也是一个相互交流的过程,如果有些地方你不清楚,他甚至会提醒你一下,所以不用紧张,也不用怕他盯着你写代码,努力去做就行了。
言归正传。
(1)HashMap法
先将一个链表全部存到Map里,然后再遍历第二个,如果有交点,那么一定能在访问到某个元素的时候检测出来
如果面试官点头,就可以手写了:
import java.util.HashMap;
public class Solution
public ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2)
if(pHead1==null || pHead2==null)
return null;
ListNode current1=pHead1;
ListNode current2=pHead2;
HashMap<ListNode,Integer>hashMap=new HashMap<>();
while(current1!=null)
hashMap.put(current1,null);
current1=current1.next;
while(current2!=null)
if(hashMap.containsKey(current2))
return current2;
current2=current2.next;
return null;
(2) 集合Set法
能用Hash,那能不能用Set呢?其实思路和上面的一样,
先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可。
public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB)
Set<ListNode> 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;
(3) 使用栈
这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,不相等的时候就找到了分界线了。这种方式需要两个O(n)的空间,所以在面试时不占优势,但是能够很好锻炼我们,所以花十分钟写一个吧:
import java.util.Stack;
public class Solution
public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB)
Stack<ListNode> stackA=new Stack();
Stack<ListNode> stackB=new Stack();
while(headA!=null)
stackA.push(headA);
headA=headA.next;
while(headB!=null)
stackB.push(headB);
headB=headB.next;
ListNode preNode=null;
while(stackB.size()>0 && stackA.size()>0)
if(stackA.peek()==stackB.peek())
preNode=stackA.pop();
stackB.pop();
else
break;
return preNode;
看到了吗,从一开始没啥思路到最后搞出三种方法,熟练掌握数据结构是多么重要!!
(4)拼接两个字符串
先看下面的链表A和B:
A: 0-1-2-3-4-5 Pa
B: a-b-4-5 Pb
如果分别拼接成AB和BA会怎么样呢?
AB:0-1-2-3-4-5-a-b-4-5
BA:a-b-4-5-0-1-2-3-4-5
我们发现最后从4开始的就是公共子节点,但是建立新的链表太浪费空间了,我们只要在每个队列访问到头之后调整一下指针就行了,于是代码就出来了:
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2)
if(pHead1==null || pHead2==null)
return null;
ListNode p1=pHead1;
ListNode 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;
(5) 差和双指针
如果你想到了这三种方法中的两个,并且顺利手写并运行出一个来,面试基本就过了,至少面试官对你的基本功是满意的。但是对方可能会再来一句:还有其他方式吗?或者说,有没有申请空间大小是O(1)的方法。
我们前面介绍过双指针,那能否用一下呢?貌似可以,但是不能直接用。
假如公共子节点一定存在第一轮遍历,假设La长度为L1,Lb长度为L2.则|L2-L1|就是两个的差值。第二轮遍历,长的先走|L2-L1|,然后两个链表同时向前走,结点一样的时候就是公共结点了。
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2)
if(pHead1==null || pHead2==null)
return null;
ListNode current1=pHead1;
ListNode 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;
if(l1>l2)
int a=0;
while(a<sub)
current1=current1.next;
a++;
if(l1<l2)
int a=0;
while(a<sub)
current2=current2.next;
a++;
while(current2!=current1)
current2=current2.next;
current1=current1.next;
return current1;
一个普通的算法,我们整出来了5种方式, 就相当于做了五道题,但是思路比单纯做5道题更加开阔。下一个题我们继续练习这种思路。
以上是关于五种方法解决两个链表第一个公共子节点的主要内容,如果未能解决你的问题,请参考以下文章