环形链表 I
判断一个链表是否带环
思路一
快慢指针法
创建一块一慢两个指针,去遍历链表,如果快指针走到了最后一个节点或走到了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;
}
思路一的问题
问题一 :为什么一定会相遇?会不会出现错过的情况?永远追不上
想解释这个问题比较简单,我们假设有一带环链表List,和快指针 fast 慢指针 slow
此图为假设 slow 指针刚进入环时,与fast指针距离为N
因为fast指针的速度是slow指针速度的两倍,所以slow每走一步,fast都会追上slow一步
它们之间的距离也会不断的缩减:N N -1 N -1 .....3 2 1 0,当N为0时,fast就追上slow了,并不会出现错过的情况
问题二 :slow一次走一步,fast一次走 3步 4步 5步 n步 可以吗 ?
这里我们只推导 slow一次走一步,fast 一次走3步的过程,其余推导过程类似
此图为假设 slow 指针刚进入环时,与fast指针距离为N
情况一:当N是偶数时 —— 直接追上
fast 追击 slow 情况:N N - 2 N - 3 ......4 2 0 刚好可以追上
情况二 :当 N 是奇数时 —— 展开新一轮的追击
结论:当 N 是奇数 且 C 为偶数时,永远也追不上
验证结论
经过推理我们得出:当 N 是奇数 且 C 为偶数时,fast 永远也追不上 slow,接下来我们尝试建立N与C的联系,来验证它是否正确
slow 刚进入圆环时,研究slow与fast走过的路程有:
slow 走的距离 :L
fast走的距离 :L + XC + C - N(X为圈数)
fast走的距离为 slow 的3倍 :3L = L + XC + C - N
化简该等式有:2L = (X + 1)C -N
将我们刚刚得出的结论(当 N 是奇数 且 C 为偶数时,永远也追不上)带入该等式,我们发现:
所以结论(当 N 是奇数 且 C 为偶数时,永远也追不上),不成立
结论
当 slow 走一步 fast 走三步时 ,fast一定追的上 slow不存在永远追不上的情况!
环形链表 ||
找环的入口点
思路一
双指针法
先找到 fast指针 与 slow指针 相遇的节点meet ,如找不到返回NULL
找到meet节点后:让一个指针从头开始走,一个指针从meet开始走(两个指针走的速度一样),当两个指针相遇时,这个节点就是入口点
代码:
struct ListNode * 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 fast;
}
}
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
//找到meet节点
struct ListNode* meet = hasCycle(head);
if(meet == NULL)
return NULL;
//让慢指针重新开始走,快指针从meet位置开始走,当它们再次相遇时,就是环的入口点
struct ListNode* cur_in = meet;//一个指针从meet开始走
struct ListNode* cur_out = head;//一个指着从链表头部开始走
while(true)
{
if(cur_in == cur_out)
{
return cur_in;
}
cur_in = cur_in->next;
cur_out = cur_out->next;
}
}
思路一的问题
为什么两个指针会在环的入口点相遇
研究快慢指针相遇走的路程时有:
slow走过的路程:L + N
fast走过的路程:L + XC + N (X大于等于1,且X为整数 )
fast走过的路程是slow的2倍:2(L + N) = L + XC + N
化简:2(L + N) = L + XC + N = L = XC - C = L = (X-1) + C - N
这个公式就是为什么两个相同速度的指针会在环的入口点相遇了:L = (X-1) + C - N
由于L = C - N所以它们才会在环的入口点相遇
思路二
在相遇点把环给解开,这样问题就变成了找两个链表相遇的问题
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode * 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 fast;
}
}
return NULL;
}
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode * curA;
struct ListNode * curB;
struct ListNode * cur;
int countA = 0;
int countB = 0;
//统计A链表总节点
cur = headA;
while(cur)
{
cur = cur->next;
countA++;
}
//统计B链表总结点
cur = headB;
while(cur)
{
cur = cur->next;
countB++;
}
//让链表长的指针走到与链表短的指针同位置
int gap = abs(countA - countB);
struct ListNode * longList = headA,*shortList = headB;
if(countB > countA)//修正长短链表
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList -> next;
}
//开始查找有没有相交的节点
while(longList) //随意哪一个走到NULL就结束
{
if(longList == shortList) //节点地址相同说明这是相交的第一个节点
{
return longList;
}
longList = longList -> next;
shortList = shortList -> next;
}
//一直没有相交的节点就返回NULL
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
//找到相遇点
struct ListNode* meet = hasCycle(head);
if(meet == NULL)
{
return NULL;
}
//解开环
struct ListNode* newhead = meet->next;
meet->next = NULL;
//找两条链表相交点
meet = getIntersectionNode(newhead,head);
return meet;
}
随机链表的复制
复制一个链表,难题在于链表中的random指针怎么去复制
由于random指针指向的是自身链表中的节点
你复制的链表节点的各自random指针也要和源链表一样,指向各自的节点
思路一
把复制链表依次插入到源链表各个节点中
这样,复制节点的random指针就相当于 == 被复制节点random指针指向的next节点
代码:
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;//将copy节点插入cur节点与cur下一个节点之间
cur -> next = copy;
cur = copy -> next;//cur走向源链表的下一个节点
}
//使用我们准备好的链表,它可以帮助我们建立random的联系
cur = head;
struct Node* copy;
while(cur)//当走到NULL时停止,其实这个是拷贝链表最后一个节点的nxte指向NULL
{
copy = cur -> next;//创建copy指向每个源链表后的拷贝节点
if(cur -> random == NULL)
{
copy -> random = NULL;
}
else
{
copy ->random = cur ->random ->next;//
}
cur = copy -> next;
}
//解开链表
cur = head;//cur遍历源链表
struct Node* source_next;//源链表的下一个节点
struct Node* copy_sentry = (struct Node*)malloc(sizeof(struct Node));//哨兵节点
struct Node* copy_tail;//拷贝链表尾节点
copy_tail = copy_sentry;
while(cur)
{
copy = cur -> next;
source_next = copy->next;
//拷贝链表的尾插
copy_tail->next = copy;
copy_tail = copy_tail -> next;
//回复源链表
cur->next = source_next;
//cur和source_next在源链表上各走一步
cur = source_next;
}
return copy_sentry->next;
}