[LeetCode] Intersection of Two Linked Lists

来源:互联网 发布:失恋巧克力职人 知乎 编辑:程序博客网 时间:2024/06/10 21:02

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不成立退出循环,返回交点指针。
如果大家理解上有困难,可以画个图模拟一下。这种想法光是理解对我们来说都挺费劲的,大佬还能用短短几行代码就将其实现,实在是让人佩服。
这道题也让我认识到,自己对指针、链表的认识还需要加强。