链表相关题解(进阶)

1.输入两个链表,找出它们的第一个公共结点
这里的示例输入是伪代码形式
链表的相交是y字型,而不是x字型。这是一个单链表,每个节点只有一个next,所以不可能有x字型。
思路:最基本的思路,看链表A中的节点是否为链表B中的节点。但这样的时间复杂度效率太低,为O(m*n);这样只有一个好处可以在确定相交后就能求出相交的节点。注意:不能用节点中的值进行比较,要用地址进行比较。
思路2:找到链表A和链表B的尾结点,比较地址是否相同,相同则链表相交。时间复杂度为O(m+n)。如何找第一个交点?逆置可以吗?不可以,逆置只能逆置一个链表,另一个链表会找不到。以示例1为例,只要链表A从第一个节点开始,链表B从第二个节点开始,遍历,第一个地址相同的节点就是交点。确定链表AB的长度(la,lb),就能保证链表在正确的位置(长的链表先走差距步( |la-lb| ))开始遍历。
代码如下:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    assert(headA && headB);
    int la = 0;
    int lb = 0;
    int i = 0;
    struct ListNode* pa = headA;
    struct ListNode* pb = headB;
    while(pa->next)
    {
        pa = pa->next;
        la++;
    }
    while(pb->next)
    {
        pb = pb->next;
        lb++;
    }
    if(pb != pa)//如果不等,说明不相交
    {
        return NULL;
    }
    if(la > lb)//A长还是B长两种情况判断
    {
        for(i = 0; i < la-lb; i++)
        {
            headA = headA->next;
        }
    }
    else
    {
        for(i = 0; i < lb-la; i++)
        {
            headB = headB->next;
        }
    }
    while(headA != headB)
    {
        headA = headA->next;
        headB = headB->next;
    }
    return headA;
}
2.给定一个链表,判断链表中是否有环(大的来了)
带环链表一遍历就会死循环。带环链表是无法计算节点数的链表。
先将带环链表抽象化:假设这就是一个带环链表
设置快慢指针,fast,slow,在slow进环后,fast开始追赶slow,直到fast追上slow。
仅仅判断是否为环是很简单的。但这个本质上是一个逻辑题。
先写出代码:
bool hasCycle(struct ListNode *head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            return true;
        }
        
    }
    return false;
}
扩展追问:
1.fast走两步,slow走一步,一定能追上吗?如何证明?
2.fast走3,4,5,n步呢?
3.求出链表的入口点。
1.假设slow进环后slow和fast的距离是N,每次追击,距离缩小1,则slow和fast之间的距离必然会减至0。
2.走3步,则不一定追的上,比如环长为8,每次追击距离缩小2,slow进环后与fast的距离为7,那么距离变化为:7,5,3,1,7,5,3,1……
就会永远追不上,走4步,也可能一直追不上,比如进环后距离为7,环长为9,距离变化为7,4,1,7,4,1……再考虑走n步的情况,假设环
长为N,slow进环后距离为C,那么如果(C+kN)/(n-1)存在 整数k使得公式(C+kN)/(n-1)的值可为整数就必定能追上,反之则不能。
另一个理解方式:走3步,如果C为偶数则必定会追上,若C为奇数,则最后的距离为-1,也就是N-1,N-1为偶数,也能追上,N-1为奇
数就永远追不上。走4步,最后的距离为2或1或0,为2,则距离为kN+2,存在k使得(kN+2)%3=0就能追上,不存在就永远追不上,同
理,存在k使得(kN+1)%3=0,就能追上。不同情况的C会导致不同情况的最后距离。
3.求slow入口点
法一:公式法
存在公式可以证明l1和l2的长度相等,也就是两个指针从头结点和快慢指针相遇点开始走,会在环的入口点相遇。
fast一次走2步,slow一次走1步,设slow走的距离为x,fast的距离为2x,设环的长度为N,因为相对速度是1,所以一定是在fast第二
圈时追上,也就是说,fast比slow多走的x就是环的长度。slow走过的距离是环的长度,所以l1,l2相等。
这样想不完全对,因为环的大小不一定,所以可能存在,fast已经走了很多圈的情况,但slow不可能走满一圈。
所以应该表示成kN = x,k为整数。小环也满足l1,l2长度相等,就是表示方式不一样。环很大则满足上面的情况。fast要追上slow至少
要走一圈。所以设环长为N,slow进环后fast要追的距离为C,头结点到slow入环点距离为L。x = L+N-C,2x = L+kN+N-C,因为2倍
关系,所以2L+2N-2C = L+kN+N-C =》 L = kN-(N-C)
所以在代码中,一个指针从头结点开始,一个指针从相遇点开始,每次前进1,地址相同的节点就是slow入环点。
代码如下:
struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            struct ListNode* meet = slow;
            while(meet != head)
            {
                meet = meet->next;
                head = head->next;
            }
            return meet;
        }
    }
    return NULL;   
}
法二
把环断开,转换成链表的相交问题。但这种方法的代码会复杂一点。
3. 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点,要求返回这个链表的深度拷贝。
深拷贝就是拷贝一样格式的链表,输入中第1个节点7的random指向null,第2个节点13的random指向第1个节点,深拷贝要保证,
random指向的节点也是一样的。难点也是在random上,保证random要指向新拷贝链表的对应节点,直接复制则指向原链表的节点。
思路:第一步拷贝原链表。第二步让random指向对应的节点。
如何指向对应的节点?
通过遍历找节点存储的值可以吗?不可以,节点可能存储相同的值,那怎么找?用相对距离。然后用快慢指针找到对应的节点。这个过
程相对复杂,而且快慢指针找负距离的相当麻烦。
所以介绍一个新方法:将新拷贝链表的每个节点分别连在原节点的后面。
拷贝节点的random在原节点random的后面。完成random后再把拷贝链表解开。然后将各拷贝节点链接到一起。
这只是思路,写代码时还要另外画图。
对照图写代码不容易出错。(标记行)
代码如下:
struct Node* copyRandomList(struct Node* head)
{
    if(head == NULL)
        return NULL;
    struct Node* cur = head;
    while(cur)//在原节点后拷贝链表
    {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;
        copy->next = cur->next;
        cur->next = copy;
        cur = cur->next->next;
    }
    cur = head;
    while(cur)//改对应的random,看标记行下第一幅图理解
    {
        struct Node* copy = cur->next;
        if(cur->random == NULL)
        {
            cur->next->random = NULL;
            cur = cur->next->next;
        }
        else
        {
            copy->random = cur->random->next;
            cur = cur->next->next;
        }
    }
    cur = head;
    struct Node* ret = cur->next;
    while(cur)//链接复制链表,看标记行下第二幅图理解
    {
        struct Node* tmp = cur->next;
        cur->next = tmp->next;
        cur = cur->next;
        if (cur != NULL)//在最后cur指向NULL了,if语句结束后就是while判断cur是否为空了,if中cur->next会非法访问
            tmp->next = cur->next;
        else
            tmp->next = NULL;
    }
    return ret; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值