前言:本博客旨在讲解两道链表经典带环问题及一道链表复制问题,并深入浅出其中原理
目录
(1)为什么一定会相遇,有没有可能会错过,即永远追不上?请证明
(2)slow一次走1步,fast走3,4,5,....,n步可以吗?
2,给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL
1,给定一个链表,判断链表中是否有环
给你一个链表的头节点 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
给定一个链表的头节点 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,给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。 要求返回这个链表的深度拷贝
给你一个长度为 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;
}