题解 | #两个链表的第一个公共结点#

两个链表的第一个公共结点

http://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46

题目中的信息:
  • 两个链表含有公共节点或没有,有公共节点则返回第一公共节点指针
  • 单链表,无循环
  • 没有公共节点返回空
举一反三:

学习完本题的思路你可以解决如下题目:

BM4.合并有序链表

BM5.合并k个已排序的链表

BM6.判断链表中是否有环

BM7.链表中环的入口节点

BM8.链表中倒数最后k个节点

BM9.删除链表的倒数第n个节点

BM13.判断一个链表是否为回文结构

BM14.链表的奇偶重排

方法一:双指针长度比较法(推荐使用)

知识点:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

如果两个链表有公共节点,那么它们后半部分都是相同的,我们要找的也就是后半部分的第一个节点。链表前半部分不同,长度也可能不同,因此同时遍历的话不一定就会同时到达第一个公共节点。

但是,如果我们能够找到长度差:

int n = p1 - p2;

较长的链表指针先走nn步:

//假设pHead1更长
for(int i = 0; i < n; i++)
    pHead1 = pHead1.next;

然后两个指针分别走剩余的步数,就会同时到达第一个公共节点。

具体做法:

  • step 1:单独的遍历两个链表,得到各自的长度。
  • step 2:求得两链表的长度差nn,其中较长的链表的指针从头先走nn步。
  • step 3:两链表指针同步向后遍历,遇到第一个相同的节点就是第一个公共节点。

图示:

图片说明

Java实现代码:

public class Solution {
    //计算链表长度的函数
    public int ListLenth(ListNode pHead){  
        ListNode p = pHead;
        int n = 0;
        while(p != null){
            n++;
            p = p.next;
        }
        return n;
    }
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        int p1 = ListLenth(pHead1);  
        int p2 = ListLenth(pHead2);
        //当链表1更长时,链表1指针先走p1-p2步
        if(p1 >= p2){  
            int n = p1 - p2;
            for(int i = 0; i < n; i++){
                pHead1 = pHead1.next;
            }
            //两个链表同时移动,直到有公共节点时停下
            while((pHead1 != null) && (pHead2 != null) && (pHead1 != pHead2)){ 
                pHead1 = pHead1.next;
                pHead2 = pHead2.next;
            }
        }
        //反之,则链表2先行p2-p1步
        else{  
            int n = p2 - p1;
            for(int i = 0; i < n; i++){
                pHead2 = pHead2.next;
            }
            //两个链表同时移动,直到有公共节点时停下
            while((pHead1 != null) && (pHead2 != null) && (pHead1 != pHead2)){
                pHead1 = pHead1.next;
                pHead2 = pHead2.next;
            }
        }
        return pHead1;
    }
}

C++实现代码:

class Solution {
public:
    //计算链表长度的函数
    int ListLenth( ListNode* pHead){  
        ListNode* p = pHead;
        int n = 0;
        while(p != NULL){
            n++;
            p = p->next;
        }
        return n;
    }
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int p1 = ListLenth(pHead1);  
        int p2 = ListLenth(pHead2);
        //当链表1更长时,链表1指针先走p1-p2步
        if(p1 >= p2){ 
            int n = p1 - p2;
            for(int i = 0; i < n; i++){
                pHead1 = pHead1->next;
            }
            //两个链表同时移动,直到有公共节点时停下
            while((pHead1 != NULL) && (pHead2 != NULL) && (pHead1 != pHead2)){ 
                pHead1 = pHead1->next;
                pHead2 = pHead2->next;
            }
        }
        //反之,则链表2先行p2-p1步
        else{ 
            int n = p2 - p1;
            for(int i = 0; i < n; i++){
                pHead2 = pHead2->next;
            }
            //两个链表同时移动,直到有公共节点时停下
            while((pHead1 != NULL) && (pHead2 != NULL) && (pHead1 != pHead2)){
                pHead1 = pHead1->next;
                pHead2 = pHead2->next;
            }
        }
        return pHead1;
    }
};

Python代码实现:

class Solution:
    #计算链表长度的函数
    def ListLenth(self, pHead): 
        p = pHead
        n = 0
        while p:
            n += 1
            p = p.next
        return n
    
    def FindFirstCommonNode(self , pHead1 , pHead2 ):    
        p1 = self.ListLenth(pHead1)
        p2 = self.ListLenth(pHead2)
        #当链表1更长时,链表1指针先走p1-p2步
        if p1 >= p2: 
            n = p1 - p2
            for i in range(n):
                pHead1 = pHead1.next
            #两个链表同时移动,直到有公共节点时停下
            while pHead1 and pHead2 and pHead1 != pHead2:
                pHead1 = pHead1.next
                pHead2 = pHead2.next
        #反之,则链表2先行p2-p1步
        else: 
            n = p2 - p1
            for i in range(n):
                pHead2 = pHead2.next
            #两个链表同时移动,直到有公共节点时停下
            while pHead1 and pHead2 and pHead1 != pHead2:
                pHead1 = pHead1.next
                pHead2 = pHead2.next
        return pHead1


复杂度分析:

  • 时间复杂度:O(n)O(n),其中n为两链表较长者的长度,虽是多次循环,但都为单循环,取最大值即为O(n)O(n)
  • 空间复杂度:O(1)O(1),常数级指针,无额外辅助空间使用
方法二:双指针连接法(扩展思路)

思路:

由上种方法长度差的思路,不同于上述一个指针先走另一个指针后走,仅需将两个链表连在一起,两个指针同步走。

p1 = p1 == NULL ? pHead2 : p1->next;   
p2 = p2 == NULL ? pHead1 : p2->next;

易知将两个链表连在一起长度都相等,对于遍历两个链表的两个指针,公共部分走的步数是一样的,非公共部分因都走了两个链表,因此也是相同的,所以绕了一圈,第一个相同的节点便是第一个公共节点。

具体做法:

  • step 1:判断链表情况,其中有一个为空,则不能有公共节点,返回null。
  • step 2:两个链表都从表头开始同步依次遍历。
  • step 3:不需要物理上将两个链表连在一起,仅需指针在一个链表的尾部时直接跳到另一个链表的头部。
  • step 4:根据上述说法,第一个相同的节点便是第一个公共节点。

图示:

图片说明

Java实现代码:

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        //其中有一个为空,则不能有公共节点,返回null
        if(pHead1 == null || pHead2 == null) 
            return null;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        //相当于遍历两次两个链表所有值
        while(p1 != p2){ 
            p1 = p1 == null ? pHead2 : p1.next;   
            p2 = p2 == null ? pHead1 : p2.next;
        }
        return p1;
    }
}

C++实现代码:

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        //其中有一个为空,则不能有公共节点,返回NULL
        if(!pHead1 || !pHead2) 
            return NULL;
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2;
        //相当于遍历两次两个链表所有值
        while(p1 != p2){
            p1 = p1 == NULL ? pHead2 : p1->next;   
            p2 = p2 == NULL ? pHead1 : p2->next;
        }
        return p1;
    }
};

Python代码实现:

class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
         #其中有一个为空,则不能有公共节点,返回NULL
        if not pHead1 and not pHead2:
            return None
        p1 = pHead1
        p2 = pHead2
        #相当于遍历两次两个链表所有值
        while p1 != p2: 
            p1 = pHead2 if p1 == None else p1.next
            p2 = pHead1 if p2 == None else p2.next
        return p1

复杂度分析:

  • 时间复杂度:O(n+m)O(n+m),其中mmnn分别为两链表的长度,因一次遍历两链表,所以为O(n+m)O(n+m),也可以看成是O(n)O(n)级别的
  • 空间复杂度:O(1)O(1),常数级指针,无额外辅助空间使用
全部评论
为啥不会出现当前节点确实相同了,但后续节点中存在不同呢?
点赞
送花
回复
分享
发布于 2022-04-22 21:09

相关推荐

感觉这一周太梦幻了,就像一个梦,很不真实~~~感觉这个暑期,我的运气占了99成,实力只有百分之一4.15上午&nbsp;腾讯csig&nbsp;腾讯云部门,面完秒进入复试状态4.16下午&nbsp;美团优选供应链部门,4.18上午发二面4.17晚上&nbsp;阿里国际一面,纯拷打,面完我都玉玉了4.18下午&nbsp;阿里国际二面,是我们leader面的我,很轻松~~4.18晚上&nbsp;约了hr面4.19上午&nbsp;hr面,下午两点口头oc4.19晚上&nbsp;意向书说起来我的暑期好像一次都没挂过~~~~~难道我是天生面试圣体?----------------------------------------------------------------------六个月前,我还是0项目0刷题,当时想的是先把论文发出来再去找实习。结果一次组会,老师打破了我的幻想(不让投B会,只让投刊或者A)我拿头投啊!!!然后就开始物色着找实习,顺便做完了mit的6.s081,但是基本上还是没刷过题目-----------------------------------------------------------------------11月&nbsp;&nbsp;一次偶然的机会,面进了某个耳机厂的手环部门,大概是做嵌入式的,用的是CPP。12月&nbsp;莫名其妙拿到了国创的面试机会,0基础四天速成java基础!居然也给我面过了hhhhh,可能是面试没写题吧入职国创后的几个月,一直没活,天天搁那看剧,都快忘了还有暑期实习这回事了~~~~命运的齿轮在2.26开始转动,因为这一天美团开了,我开始慌了,因为那时的我什么都不会。lc,八股,sql全部是0进度。然后就开始了女娲补天,上班刷题,下班继续做之前的开源,顺便学一学八股。3月到现在,lc也刷到快200了,一天最多提交了47次~~~~~~~~~~八股根据别人的面经总结和博客,写了快十万字的笔记~~~~~~~~~~简历上的实习经历和开源,也努力去深挖了,写了几万字的记录~~~~~~所以面试的时候,基本上都能cover了,面试官问到的基础基本都会,不基础的我就把他往我会的地方引。结果好像还不错,基本上每个面试官评价都挺好的emmmmmmmm
投递阿里巴巴等公司10个岗位
点赞 评论 收藏
转发
4 3 评论
分享
牛客网
牛客企业服务