目录
- 相交链表
- 环形链表
- 扩展问题
- 环形链表||
- 原理
- 随机链表的复制
相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
思路:
1.想要判断两个链表相交的话,就得找到他的相交节点,但是如果两个链表都从自己起始位置开始找的话如:
2.求长短链表的差值,让长链表走差值步
如果其中一个链表比另一个链表长就始终会比短的链表少走一步,这里我们不妨算出两个链表的长度,求他们的差值,让长的链表先走差值步,再长链表和短链表一起走就行了。
这样就会遇到相交节点了
3.若链表不相交,则不会遇到相交节点,最后会遍历到各自NULL.
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
int n1 = 0;
int n2 = 0;
ListNode* pcur1 = headA,*pcur2 = headB;
while(pcur1)
{
n1++;
pcur1 = pcur1->next;
}
while(pcur2)
{
n2++;
pcur2 = pcur2->next;
}
int gap = abs(n1-n2); //绝对值,有可能n1比n2小
ListNode* ShortList = headA;
ListNode* LongList = headB;
if(n1 >n2)
{
LongList = headA;
ShortList = headB;
}
while(gap--)
{
LongList = LongList->next;
}
while(ShortList && LongList)
{
if(LongList ==ShortList)
{
return LongList;
}
ShortList = ShortList->next;
LongList = LongList->next;
}
return NULL;
}
注意:我们判断谁是长链表可以直接通过他们的长度谁大来判断
环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
思路:
这道题思路有点不好想,直说就是用到快慢指针,两个指针都从头结点开始遍历,慢指针走一步,快指针走两步,最坏这两个指针一定会在一个节点相遇.
为什么一定会相遇呢:
每次fast和slow走的时候,fast走两步,slow走一步,他们的距离都缩小1,n-1直到n-n=0就相遇了
扩展问题
这里我们再深入一下问题,如果快指针一次走3步,4步,n步呢?还一定会相遇吗?
这里我们先看快指针一次走3步的情况:
这里原本是fast追slow,现在变成了slow追fast,这就出现了套圈。
他们每次距离:N,N-2,N-4,N-6.这里如果N是偶数的话会相遇,但如果N为奇数如上例就会出现套圈。此时套圈后他们之间的距离为:C - 1
那么套圈后他们还有可能相遇吗?
我们这里证明N为奇数套圈的情况:
(1)套圈后的距离(C-1为偶数会相遇
(2)套圈后的距离(C-1)为奇数不相遇
下面我们证明C-1为奇数不相遇的情况
这里我们再来看一个图:
L为入环的距离,N为fast和slow之间的距离,那么fast走的距离就是C-N(这里设环的周长为C)
slow走的距离:L
fast走的距离:L+XC+C-N 这里xc表示有可能fast已经走了很多圈,加上L如果L很长的话最好加上。
又因为3slow=fast
有3L=L+XC+C-N
化简:2L=(X+1)C-N
证明C-1为奇数不相遇的情况
左边:偶数*任何数=偶数 右边:这里N已经为奇数,那么只有奇数-奇数为偶数
也就是(X+1)C是一个奇数,
c
c-1是奇数,那么C是一个偶数,偶数乘任何数都等于偶数,不满足等式的性质(偶数=偶数)(这里C是偶数就是偶数-奇数,上面说了是奇数-奇数)。
所以这里不存在C-1为奇数的情况,所以他们一定会相遇。
环形链表||
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路:如果一个指针从头结点开始一步一步走,一个指针从相遇点一步一步走,那么他们一定会在入环节点相遇。
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* slow,*fast;
slow = fast = head;
while(fast && fast->next)
{
//找到相遇点
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{ //进了这里链表一定有环
//从头结点和相遇点开始同时一步一步遍历一定会在入环节点相遇
ListNode* pcur = head;
while(pcur != fast)
{
pcur = pcur->next;
fast = fast->next;
}
return pcur;
}
}
return NULL;
}
原理
就是证明:L=C-X
注意上面的N是周期
随机链表的复制
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
注意:
- 深拷贝就是新建一个链表复制过来。
- 复制链表中的指针都不应指向原链表中的节点 。
或许有人认为,简单嘛,直接创建一个新链表,定义一个指针指向原链表开始遍历一个一个复制到新链表,遍历到NULL为止。如果是链表的指针只有一个那这样是对的
但是我们很明显的看出来,这个链表不光有next指针,还有一个random指针,并且这个指针是随机的,可以指向自己或者链表中的任何一个节点还有NULL,所以我们单纯的复制是行不通的。
思路:
1.将复制的节点尾插到旧链表每个对应节点的后面,并且把旧链表的next指针指向新链表的对应节点。
‘如:
’原节点1不再指向原原节点2,而是指向新节点1
2.设置random指针,这就体现了我们为什么把新链表的节点尾插到旧链表中相对应的节点的好处,我们可以直接通过变量旧链表->random->next指针找到新链表中的random指针的位置。
3.将旧链表和新链表之间断开
typedef struct Node Node;
Node* BuyNode(int x)
{
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = x;
newnode->next = newnode->random = NULL;
return newnode;
}
void AddNode(Node* phead)
{
Node* pcur = phead;
while(pcur)
{
Node* Next = pcur->next;
//创建新节点,尾插到pcur
Node* newnode = BuyNode(pcur->val);
pcur->next = newnode;
newnode->next = Next;
pcur = Next;
}
}
struct Node* copyRandomList(struct Node* head) {
if(head == NULL)
{
return NULL;
}
//复制原链表的节点并且将他们之间进行连接
AddNode(head);
//设置random指针
Node* pcur = head;
while(pcur)
{
Node* copy = pcur->next;
if(pcur->random != NULL)
{
copy->random = pcur->random->next;
}
pcur = copy->next;
}
//断开原链表和旧链表的连接
pcur = head;
Node* NewHead,*NewTail;
NewHead = NewTail = pcur->next;
while(pcur->next->next)
{
pcur = pcur->next->next;
NewTail->next = pcur->next;
NewTail = NewTail->next;
}
return NewHead;
}
注意第一个节点的random指针指向NULL,但是我们创建节点的时候就已经把random指针设置NULL不需要单独处理