五种方法解决两个链表第一个公共子节点

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道题更加开阔。下一个题我们继续练习这种思路。

以上是关于五种方法解决两个链表第一个公共子节点的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer链表第一个公共子结点

剑指Offer - 两个链表第一个公共节点

两个链表第一个公共点

剑指Offer35 两个链表第一个公共结点

剑指offer36 两个链表的第一个公共子节点

链表6:两个链表的第一个公共子节点