Day 7 面试题链表相交 142环形链表 链表总结篇

面试题链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交

​编辑

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

​编辑

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

​编辑

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

​编辑

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

        这个题和上面一个删除倒数第n个结点有点类似,采用双指针法。上一个删除倒数第n个结点是利用快指针先走,慢指针后走卡出了倒数第n个节点,本题一共有“两个链表”,但是如果同时遍历,可能从头到尾也找不到连在一起的结点,所以让长链表的cur指向一个结点,使这个节点后面的节点数与短链表的节点数都相等,最后再遍历,这样可以实现匹配度。

        具体实现方法是计算两个链表的长度,做差值,让长链表的cur移动到差值处,之后长短链表一起遍历,这样能够确保遍历的时候能够配准,如果到最后也没有配准,说明两个链表没有交点。时间复杂度主要在于寻找链表长度上,为O(m+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) {
        int lenA=0,lenB=0;
        ListNode*curA=headA;
        ListNode*curB=headB;
        while(curA!=nullptr)
        {
            curA=curA->next;
            lenA++;
        }
        while(curB!=nullptr)
        {
            curB=curB->next;
            lenB++;
        }
        curA=headA;
        curB=headB;
        int public_len=lenA>lenB?(lenA-lenB):(lenB-lenA);
        if(lenA>lenB)
            {
                while(public_len)
                {
                    curA=curA->next;
                    public_len--;
                }
                
            }
        else if(lenA<lenB)
            {
                while(public_len)
                {
                    curB=curB->next;
                    public_len--;
                }
            }
        while(curA!=nullptr&&curB!=nullptr)
        {
            if(curA==curB) return curA;
            curA=curA->next;
            curB=curB->next;
        }
        return nullptr;
       
    }
};

        本题注意的一点就是要让两个链表对准,和上一个寻找倒n结点有异曲同工之妙,都是利用双指针法,不过这道题是快指针先移动完,最后和慢指针一起移动。

142 环形链表

 

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

         这个题到手以后,可以说是一点思路都没有了,能想到可能需要两个指针,但是怎么转圈才能转到一起呢,没有想到可以给指针设置速度。但是重合条件也是有点懵逼的······

        本题可以拆解成两个:1.判断是否有环 2.环的入口在哪里

        分别定义fast和slow指针,fast每次移动两个节点,slow每次移动一个节点,如果两个指针在中途相遇,那就一定能够证明有环(否则一个速度慢的怎么可能追上一点速度快的呢),并且二者一定在环中相遇。

        

        现在可以知道这个链表里面是否有环了,接下来要找到这个环的入口,如下图所示设置xyz。

 

         那么相遇时,slow走过的距离为x+y,fast走过的距离是x+y+n(y+z),n为fast指针在环内走了n圈才遇到slow指针,y+z为一圈内的结点个数。

        这里有个疑问,既然fast里面带n,为什么slow里面不是带有若干环的长度呢(也转好几圈)。fast的速度是slow的二倍,当slow要是想走完一圈,fast一定已经走完两圈了,两圈之内怎么可能追不上slow呢,此时slow还在一圈以内,有人说玩意fast把slow给错开了呢,但是fast相对slow的速度是1,所以不可能跳过去的。

        因为fast一次走两个节点,slow走一个,所以fast走过的结点数永远等于slow的二倍,于是有2(x+y)= x+y+n(y+z);要找到的是环形的入口,所以需要提出x,整理一下变为x=n(y+z)-y,最好不要让里面出现负数,所以变为x = (n - 1) (y + z) + z  ;注意n一定大于等于1,因为fast指针至少多走一圈才能与slow指针相遇。第一次相遇时,只需令n等于1,公式就变为了x=z。这就意味着,从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候的结点就是环形入口的结点,所以在相遇处定义一个index1,在头结点处定义一个index2,动画如下:

         寻找相遇结点的过程是经过数学推理出来的,也就是说要保持fast和slow分别为2和1的速度才可以这样,其他的情况肯定不能这么应用的。时间复杂度为n,代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode*fast=head, *slow=head;
        while(fast!=nullptr&&fast->next!=nullptr)
        {
            fast = fast->next->next;
            slow = slow -> next;
            if(fast==slow)
            {
                ListNode*index1=fast;
                ListNode*index2=head;
                while(index1!=index2)
                {
                    index1=index1->next;
                    index2=index2->next;
                }
                return index1;
            }
        }
        return nullptr;
    }
};

链表总结篇

        链表的头结点是比较难处理的,通常要设置一个虚拟头结点,这样就不用分类,保证了代码的连贯性。

        同时,单链表和双链表的增删改查也要牢记于心,建议也要使用虚拟头结点。

       反转链表时候,主要有迭代法,递归法,头插法,重点就是一小块一小块的进行改造,要么改变顺序,要么拆网的时候织网,需要注意的一点就是随时设置tmp,以防这个节点没法被找到。

        删除链表倒数第n个结点和链表相交,采用的都是双指针法,注意判断双指针的停止条件即可。

        最后的环形链表,比较难的部分就是数学证明,同时也要有一些思路,不能看到难题直接发呆。

        总之,链表这一章的算法其实并不难,但是要注意不要出现bug,要熟悉各种操作方法,特别抽象的题要想想数学证明(第一次遇到),其他的只要想想逻辑就好。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值