链表的带环问题 链表的深度拷贝

1.1. 链表是否带环

代码很简单,最主要就是如何证明

  1. 首先判断链表是否带环,可以定义两个指针,一个快指针一个慢指针。快指针走两步,慢指针走一步
  2. 一定会相遇吗?有没有可能会超过?
  3. 假设进环的时候fast和slow的相隔距离是N,每走一步就是N - 1 当到了0的时候就会追上
  4. 意思就是说:每次追击,距离都会-1,此时不管是偶数环还是奇数环都没关系
  5. 这里用动图来解释一下,在这个动图中C = 5

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow = head,*fast = head;
    while(fast && fast->next)//假设没有环,正常链表的情况下
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return true;
    } 
    return false;
}

1.2. 问题2

  1. 此时用动图来说明问题,N分为偶数和奇数的情况,但是这个建立在slow走1步,fast走3步的情况
  2. fast走3步,意味着每次的追击距离 - 2
  3. 下面这个表示为偶数的情况

  1. 下面这个是奇数的情况,当追过头的时候C - 1 = 偶数,下一轮就能追上

  1. slow走1步,fast 走3步、4步、5步,可以吗?一定会追上吗?请证明
  2. 假如同时存在N为奇数,C为偶数那么就永远追不上了

  1. 满足条件的:
    1. 为偶数个时候就能追上
    2. N是奇数的时候第一轮会超过1个,第二趟C-1为偶数 就可以追上了
  2. 所以终究还是有机会追上的对吗?(doge)

1.3. 返回链表开始入环的第一个节点

  1. 推导过程,根据公式推导L = (X - 1)* C + C - N;
  2. x 表示: slow进环前走了多少圈 ,C 是一圈的距离。x最少走一圈,不然等式就不会成立

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head,*fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(fast == slow)//此时说明是带环链表并且相遇了
        {
           struct ListNode* meet = fast;
            while(head != meet)
            {
                head = head->next;
                meet = meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

1.3.1方法2

  1. 在开始之前,先说一下相交链表,然后再实现第二个方法
  2. 相交链表,题目链接

  1. 思路
    1. 首先判断是否相交?遍历链表找到尾节点
    2. 再找尾节点的同时顺便记录两个链表的长度
    3. 两个链表相减,用abs();函数得出绝对值,让长链表走相减步数,使得和短链表长度一致
    4. 最后让两个链表不相等的表达式做出判断。假如相等跳出循环,得出的就是相交的起始节点
  1. 注意:修改代码时需要想到被影响的值也需要进行修改
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA = headA,*curB = headB;
    int lenA = 1,lenB = 1;//这里+1,因为遍历链表时,满足条件最后一个节点不进入
    while(curA->next)//这里是找未节点
    {
        curA = curA->next;
        lenA++;
    }
    while(curB->next)
    {
        curB = curB->next;
        lenB++;
    }
    if(curA != curB)
        return NULL;
    //假设法
    int Dval = abs(lenA - lenB);//求绝对值
    struct ListNode* LongList = headA,*ShortList = headB;//这个假设 headA是长链表
    if(lenB > lenA)//假设失败,headB更长,此时让 B成为长链表
    {
        LongList = headB;
        ShortList = headA;
    }
    while(Dval--)//走差值步
    {
        LongList = LongList->next;
    }
    while(LongList != ShortList)
    {
        LongList = LongList->next;
        ShortList = ShortList->next;
    }
    //走到这说明前面条件都满足了
    return LongList;
    //return headA;更改代码有很多地方可以都有牵连,这里就是,经量不要拿原指针,以防找不到最初指向
}
  1. 就是快慢指针相遇后,断开meet节点,创建一个新的节点,新的节点拿到meet->next,指向的下一个节点的地址就行。可以发现最后变成了相交链表的问题

  2. 调用上面的相交链表的函数,返回的节点就是,入环时的第一个节点

struct ListNode* ListIntersectNode(struct ListNode* listA,struct ListNode* listB)
{
    struct ListNode* curA = listA,*curB = listB;
    int lenA = 0,lenB = 0;
    while(curA)//找共同尾节点
    {
        curA = curA->next;
        lenA++;
    }
    while(curB)
    {
        curB = curB->next;
        lenB++;
    }
    int k = abs(lenA - lenB);
    //假设法
    struct ListNode* longlist = listA,*shortlist = listB;
    if(lenB > lenA)
    {
        longlist = listB;
        shortlist = listA;
    }
    //让长的链表走差值步
    while(k--)
    {
        longlist = longlist->next;
    }
    while(longlist != shortlist)
    {
        longlist = longlist->next;
        shortlist = shortlist->next;
    }
    return longlist;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head,*fast = head;
    while(fast && fast->next)//不是带环链表时
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)//此时相遇
        {
            struct ListNode* meet = fast;
            struct ListNode* newnode = meet->next;
            meet->next = NULL;
            return ListIntersectNode(head,newnode);
        }
    }
    return NULL;
}

2. 链表的深度拷贝

  1. 题目的要求是说拷贝对应原节点的值,并且和原链表的指向表示相同的方向
  2. 注意:每个节点一定不可以指向原链表,要指向拷贝链表

  1. 第一步,建立联系
    1. 首先在原链表每个节点后面都插入一个节点,拷贝前一个值
    2. 然后和在这个两链表之间插入,与之和这个原链表建立联系
  2. 第二步,解决random指向
    1. random的指向是随机的所以我们可以通过前一个节点来访问对应指向,保存copy位置节点
    2. random的情况可能指向为NULL,正常有节点的情况 看到图中
    3. 可以得出copy->random = cur->random->next ;这样就指向了之前拷贝在节点后的 7 了,这样拷贝的链表就建立了联系
  3. 分离链表
    1. 这个通过尾插的方式来把原链表上复制的几点取下来,所以需要一个头节点和尾节点 为什么?因为题目要求最后要返回一个头节点,而我们要尾插节点 
    2. 看上面的图,这里创建两个 copy的节点和一个after节点,刚好有三个节点还可以顺便还原 原链表
    3.  只要尾插到后面就行了,链表尾插还不会的可以去看看之前的博客,这里详细的讲了如何尾插--> 单链表的文章

struct Node* copyRandomList(struct Node* head) {
    if(head == NULL)
    return NULL;
    struct Node* cur = head;
    while(cur)
    {
        struct Node* CopyNode = (struct Node*)malloc(sizeof(struct Node));
        CopyNode->val = cur->val;//拷贝值
        //链接前后节点
        CopyNode->next = cur->next;
        cur->next = CopyNode;
        
        cur = CopyNode->next;//跳到copy的后一个节点
    }
    //解决random的指向
    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;
        if(cur->random == NULL)//原链表指向NULL,拷贝的链表也指向NULL
        {
            copy->random = NULL;
        }
        else//本题中精华代码部分
        {
            copy->random = cur->random->next;
        }
        cur = copy->next;
    }
    //分离链表
    struct Node* copyhead = NULL,*copytail = NULL;
    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;//图中所标是第二次遍历时的位置
        struct Node* after = copy->next;

        if(copyhead == NULL)
        {
            copyhead = copytail = copy;
        }
        else//尾插
        {
            copytail->next = copy;
            copytail = copytail->next;//因为前面已经尾插了一个数据,所以到下一个位置
        }
        //链表的恢复
        cur->next = after;
        cur = after;
    }
    return copyhead;
}

  • 27
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值