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

LeetCode原题里好像没有原题,但是在剑指offer里和其他高频面试题里有,笔者曾经面京东时也手写过。题目的表述有多种方式,意思都是一样的:

输入两个链表,找出它们的第一个公共节点。

例如下面的两个链表:

两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。

1.没有思路时该怎么解题

这种问题该怎么入手呢?如果一时想不到该怎么办呢?其实这时候我们可以将常用数据结构和常用算法都想一遍,看看哪些能解决问题。

常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。

我们先在脑子里快速过一下谁有可能解决问题。首先想到的是蛮力法,类似于冒泡排序的方式,将第一个链表中的每一个结点依次与第二个链表的进行比较,当出现相等的结点指针时,即为相交结点,但是这种方法时间复杂度高,而且有可能只是部分匹配上,所以还有要处理复杂的情况。排除!

其次Hash呢?模模糊糊感觉行的,OK。

之后是集合呢?和Hash一样用,目测也能解决,OK。

队列和栈呢?貌似队列没啥用,但是栈能解决问题,于是就有了第三种方法。

其他的几种结构或者算法呢?貌似都不太好用。这时候我们可以直接和面试官说,应该可以用HashMap做,另外集合和栈应该也能解决问题。面试官很明显就问了,怎么解决?

那这时候你可以继续考虑HashMap、集合和栈具体应该怎么解决,假如错了呢?比如你说队列也行,但是后面发现根本解决不了,这时候直接对面试官说“队列不行,我想想其他方法”,一般对方就不会再细究了。

算法面试本身也是一个相互交流的过程,如果有些地方你不清楚,他甚至会提醒你一下,所以不用紧张,也不用怕他盯着你写代码,努力去做就行了。

言归正传。

第一种:HashMap法

先将一个链表全部存到Map里,然后再遍历第二个,如果有交点,那么一定能在访问到某个元素的时候检测出来

 如果面试官点头,就可以手写了:

import java.util.HashMap;public class Solution {    public ListNode FindFirstCommonNode(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;    }}

第二种:集合Set法

能用Hash,那能不能用Set呢?其实思路和上面的一样,

先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可。

 代码:

public ListNode getIntersectionNode(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;}

第三种:使用栈

这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,不相等的时候就找到了分界线了。这种方式需要两个O(n)的空间,所以在面试时不占优势,但是能够很好锻炼我们,所以花十分钟写一个吧:

import java.util.Stack;public class Solution {    public ListNode getIntersectionNode(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;    }}看到了吗,从一开始没啥思路到最后搞出三种方法,熟练掌握数据结构是多么重要!!

2.更有思维含量的方法

如果你想到了这三种方法中的两个,并且顺利手写并运行出一个来,面试基本就过了,至少面试官对你的基本功是满意的。但是对方可能会再来一句:还有其他方式吗?或者说,有没有申请空间大小是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;    }

方法五:拼接两个字符串

先看下面的链表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

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种方式, 就相当于做了五道题。后面我们还会反复训练这里面涉及的一些技能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值