优雅永不过时——leetcode106 相交链表

又许久未发文章了,前阵子忙着期末考和杂七杂八的事情,暑假又在学车。

有些题目令你瞠目结舌的并不是困难的思维,而是优雅的做法。虽然这些题目并不难,但是你第一次做的时候总会被这种做法惊讶得拍手叫好。

今天要讲的是leetcode106题——相交链表。题目链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/。这道题的意思是这样的:有两个单链表,这两个链表可能有两个相交的地方,如果有,返回第一个相交的节点,没有则返回NULL。

这道题一个非常容易想到的做法是使用一个集合,先把第一个链表的节点一个个存到集合里面,再去扫描第二个链表,看看当前的节点是不是在集合里面。由于第二个链表是从前往后扫的,所以最先找出来的相交节点一定是第一个。

代码如下:

/**
 * 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;
        ListNode* cur=headA;
        while(cur!=NULL){
            s.insert(cur);
            cur=cur->next;
        }
        cur=headB;
        while(cur!=NULL){
            if(s.count(cur))
                return cur;
            cur=cur->next;
        }
        return NULL;
    }
};

如果这道题就到这了的话,那就完全没有必要讲,因为我相信大家都会这个写法。但是真正精彩的在后头:双指针。

由于解法1使用了一个集合,所以空间复杂度是 O(n)的,而时间复杂度是O(n+m)的(n是链表1的长度,m是链表2的长度),在时间这方面已经无法优化了,而空间其实是可以达到 O(1)的。

下面来说说这个解法2:
首先定义两个指针p1和p2分别指向链表1和链表2的头部,两个指针同时向后移动,如果p1为空,就把p1指向链表2的头部,如果p2为空,就把p2指向链表1的头部。直到p1==p2结束,那么p1就是答案。

为什么这样做是正确的呢?下面把所有的情况分成3种来看

情况1:两个链表根本无交点

在这里插入图片描述
在这里插入图片描述

最后两个指针都到达NULL,返回NULL。

情况2:链表1和链表2在同样的第i个节点相交
在这里插入图片描述
在这里插入图片描述
两个指针到达8的时候相等,最后返回8

情况3:链表1第i个节点和链表2第j个节点相交(i!=j)
在这里插入图片描述
在这里插入图片描述

两个指针在2这个节点相遇,最后返回2

情况1和情况2没什么好说的,接下来证明情况3的正确性:

设n是链表1的长度,m是链表2的长度,链表1距离第一个交点的距离是k,当p2走了m步的时候,p2到达链表1的头部,再走k步到达第一个交点。那么此时的p1也走了m+k步在这里插入图片描述
我们计算一下p1第二次到达第一个交点的距离为n+m-(n-k),化简为m+k步。因此这个时候我们的p1和p2指针都到达了第一个交点。p1==p2,返回p1就是最终的答案。

代码如下:

/**
 * 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 *a=headA,*b=headB;
        while(a!=b){
            a=a==NULL?headB:a->next;
            b=b==NULL?headA:b->next;
        }
        return a;
    }
};

虽然这道题的代码很短,但是这个思路的确让人回味无穷~,继续加油:)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值