链表经典算法(3)详解链表的带环问题及随机链表的复制

前言:本博客旨在讲解两道链表经典带环问题及一道链表复制问题,并深入浅出其中原理

目录

1,给定一个链表,判断链表中是否有环

思路

参考代码

思考

(1)为什么一定会相遇,有没有可能会错过,即永远追不上?请证明

(2)slow一次走1步,fast走3,4,5,....,n步可以吗? 

2,给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL

思路 

参考代码

思考

 ​编辑


 

1,给定一个链表,判断链表中是否有环

141. 环形链表 - 力扣(LeetCode)

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos 为 -1 或者链表中的一个 有效索引 。

思路

快慢指针

若不带环,则快指针或快指针next指向NULL

若带环,则快指针追上慢指针

参考代码

bool hasCycle(struct ListNode *head) 
{
    struct ListNode* fast = head, * slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(fast == slow)
        {
            return true;
        }
    }
    return false;
}

思考

(1)为什么一定会相遇,有没有可能会错过,即永远追不上?请证明

假设slow进环时,fast跟slow距离为N

故fast追击slow过程中距离变化为

N

N-1

N-2

......

2

1

0

每追击一次,距离缩小1,距离为0即为追上

(2)slow一次走1步,fast走3,4,5,....,n步可以吗? 

假设slow进环时,fast跟slow距离为N

fast追击slow过程中距离变化为

N为偶数                     

N

N-2

N-4 

......

4

2

0

每追击一次,距离缩小2,距离为0即为追上

N为奇数

N

N-2

N-4

......

3

1

-1

距离为-1:错过了,进入新一轮追击,距离为C-1(C为环长度)

1)C-1为偶数,追上

2)C-1为奇数,追不上

总结:

1 N为偶数,第一轮就追上

2 N为奇数,第一轮追不上,距离变成C-1

      a,C-1为偶数,追上

      b,C-1为奇数,追不上

其他情况类似

那么是否同时存在N为奇数 且C为偶数呢?

假设slow进环时,slow和fast距离为N,环长度为C

slow走的距离为 L

fast走的距离为 L + x * C + C-N

fast走的距离是slow的三倍

即3*L =  L + x * C + C-N    ----------->2L = (X+1)*C-N 

 2L = (X+1)*C-N 

若同时存在N为奇数 且C为偶数呢

左边一定为偶数 = (x+1)*偶数-奇数        不可能成立

故不存在N为奇数 且C为偶数

真实情况为

N是奇数且C是奇数

N是偶数且C是偶数

结论:一定能追上 

N偶数第一轮就追上

N奇数第一轮追不上,C-1偶数第二轮追上

2,给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL

142. 环形链表 II - 力扣(LeetCode)

给定一个链表的头节点  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
解释:链表中没有环。

提示:

  • 链表中节点的数目范围在范围 [0, 104] 内
  • -105 <= Node.val <= 105
  • pos 的值为 -1 或者链表中的一个有效索引

思路 

先利用快慢指针找出相遇点meet,再将meet与head依次往后直到同一点即为入环的第一个节点

参考代码

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* fast = head,*slow = 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;
}

思考

 

慢指针有没有可能走了一圈?

若慢指针走了一圈,快指针走了两圈,肯定会追上

故慢指针不可能超过一圈 

相遇时

slow走的路程: L + N

fast走的路程 : L + x*C + N (x至少为1)

fast走的路程是slow的两倍

2*(L + N) = L + x*C + N

L = x * C - N

可化为 L = (X - 1)*C + C - X

L为head走的距离  即head到入环的第一个节点

(X - 1)*C + C - X为meet 走x-1圈再走C-X距离  即meet到入环的第一个节点

思路二

newhead = meet->next;

meet->nxt = NULL;

转换为链表的相交问题(参考往期博客)链表经典算法(2)-CSDN博客 

参考代码

 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    struct ListNode * curA = headA,*curB = headB;
    int lenA = 1, lenB = 1;
    while(curA->next)
    {
        curA = curA->next;
        ++lenA;
    }
    while(curB->next)
    {
        curB = curB->next;
        ++lenB;
    }
    //尾结点不相等就不相交
    if(curA != curB)
    {
        return NULL;
    }
    //长的先走差距步,再同时走,第一个相交的就是交点
    //假设法
    int gap = abs(lenA - lenB);
    struct ListNode* longList = headA,*shortList = headB;
    if(lenB > lenA)
    {
        longList = headB;
        shortList = headA;
    }
    while(gap--)
    {
        longList = longList->next;
    }
    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }
    return longList;
    
}

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            struct ListNode * meet = slow;
            struct ListNode * newhead = meet->next;
            meet->next = NULL;

            return getIntersectionNode(head,newhead);
        }
    } 
    return NULL;
}

3,给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。 要求返回这个链表的深度拷贝 

138. 随机链表的复制 - 力扣(LeetCode)

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.random 为 null 或指向链表中的节点。

深拷贝: 拷贝一个值和指针指向跟当前链表一模一样的链表

思路

1,把拷贝节点插入在原节点的后面

拷贝节点和原节点建立一种联系关系

2,遍历随机节点

3 ,把拷贝节点取下来尾插成为新节点,然后恢复原链表

参考代码

struct Node* copyRandomList(struct Node* head) 
{
    //遍历新节点
    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 = copy->next;
    }
    cur = head;
   //遍历随机节点
    while(cur)
    {
        struct Node *copy = cur->next;
        if(cur->random == 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* next = copy->next;
        if(copytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }

        //cur->next = next;
        cur = next;
    }
    return copyhead;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值