[LeetCode] Intersection of Two Linked Lists

本文探讨如何在两个单链表开始相交的节点处找到该节点。通过分析不同方法的时间和空间复杂度,最终提出了一种优雅且高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Write a program to find the node at which the intersection of two singly linked lists begins.


For example, the following two linked lists:

A:          a1 → a2
                   ↘
                     c1 → c2 → c3
                   ↗            
B:     b1 → b2 → b3

begin to intersect at node c1.


Notes:

  • If the two linked lists have no intersection at all, return null.
  • The linked lists must retain their original structure after the function returns.
  • You may assume there are no cycles anywhere in the entire linked structure.
  • Your code should preferably run in O(n) time and use only O(1) memory.

要说在LeetCode上刷题最让我受益匪浅的地方,大概就是链表相关的问题了吧。以前对于链表的认识很浅,基本只够应付“说说链表和数组的优缺点”这种问题,真正应用到的机会很少。来了LeetCode才发现,链表还可以这么玩,而且玩的这么溜。

针对这道题,我想了很久都想不到O(n)时间复杂度的做法(我是真的菜),于是就撸了一个暴力枚举的做法:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* tempA;
        ListNode* tempB;
        while(headA)
        {
            tempA=headA;
            tempB=headB;
            while(tempB)
            {
                if(tempA==tempB)return tempA;
                tempB=tempB->next;
            }
            headA=headA->next;
        }
        return NULL;
    }
};
毕竟跟指针相关的代码很容易出错,为了确认这样写的逻辑有没有问题,我就试探性地交了一发,居然直接AC了...这要是在ACM,怎么也是TLE吧。
感觉打ACM的人是不是真的不应该做LeetCode,做多了AC多了,你真的就没有对罚时的恐惧了,对时间复杂度也视而不见了...

当然这道题还没有结束,上面的代码明显不是最优的。想了一下其实用哈希基本就可以降复杂度了,于是乎再来一发:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> s;
        while(headA)
        {
            s.insert(headA);
            headA=headA->next;
        }
        while(headB)
        {
            if(s.find(headB)!=s.end())return headB;
            headB=headB->next;
        }
        return NULL;
    }
};
哈哈哈过了,用时也还算低,但是直觉告诉我这不是出题人的本意,肯定还有更巧妙的做法。果不其然,围观了一下大佬的做法,简直变身小迷弟给大佬疯狂打call。这是大佬的代码:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA||!headB) return NULL;
        ListNode *a=headA,*b=headB;
        while(a!=b)
        {
            a=a?a->next:headB;
            b=b?b->next:headA;
        }
        return a;
    }
};
两个字:优美
题目保证不出现环,然后大佬就把两个链首尾相连构造成环。有两个理解上的要点:
  • 假设A长度m,B长度n,两个指针都会在m+n次循环后同时抵达空指针,使得a!=b不成立退出循环,防止了死循环的出现,返回NULL。
  • 假设倒数第k个点是交集,由于交点之后的点是共用的,所以两个指针都会在m+n-k次循环后抵达相同节点,使得a!=b不成立退出循环,返回交点指针。
如果大家理解上有困难,可以画个图模拟一下。这种想法光是理解对我们来说都挺费劲的,大佬还能用短短几行代码就将其实现,实在是让人佩服。
这道题也让我认识到,自己对指针、链表的认识还需要加强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值