1.1. 链表是否带环
代码很简单,最主要就是如何证明
- 首先判断链表是否带环,可以定义两个指针,一个快指针一个慢指针。快指针走两步,慢指针走一步
- 一定会相遇吗?有没有可能会超过?
- 假设进环的时候fast和slow的相隔距离是N,每走一步就是N - 1 当到了0的时候就会追上
- 意思就是说:每次追击,距离都会-1,此时不管是偶数环还是奇数环都没关系
- 这里用动图来解释一下,在这个动图中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
- 此时用动图来说明问题,N分为偶数和奇数的情况,但是这个建立在slow走1步,fast走3步的情况
- fast走3步,意味着每次的追击距离 - 2
- 下面这个表示为偶数的情况
- 下面这个是奇数的情况,当追过头的时候C - 1 = 偶数,下一轮就能追上
- slow走1步,fast 走3步、4步、5步,可以吗?一定会追上吗?请证明
- 假如同时存在N为奇数,C为偶数那么就永远追不上了
- 满足条件的:
- 为偶数个时候就能追上
- N是奇数的时候第一轮会超过1个,第二趟C-1为偶数 就可以追上了
- 所以终究还是有机会追上的对吗?(doge)
1.3. 返回链表开始入环的第一个节点
- 推导过程,根据公式推导L = (X - 1)* C + C - N;
- 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
- 在开始之前,先说一下相交链表,然后再实现第二个方法
- 相交链表,题目链接
- 思路:
-
- 首先判断是否相交?遍历链表找到尾节点
- 再找尾节点的同时顺便记录两个链表的长度
- 两个链表相减,用abs();函数得出绝对值,让长链表走相减步数,使得和短链表长度一致
- 最后让两个链表不相等的表达式做出判断。假如相等跳出循环,得出的就是相交的起始节点
- 注意:修改代码时需要想到被影响的值也需要进行修改
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;更改代码有很多地方可以都有牵连,这里就是,经量不要拿原指针,以防找不到最初指向
}
-
就是快慢指针相遇后,断开meet节点,创建一个新的节点,新的节点拿到meet->next,指向的下一个节点的地址就行。可以发现最后变成了相交链表的问题
-
调用上面的相交链表的函数,返回的节点就是,入环时的第一个节点
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. 链表的深度拷贝
- 题目的要求是说拷贝对应原节点的值,并且和原链表的指向表示相同的方向
- 注意:每个节点一定不可以指向原链表,要指向拷贝链表
- 第一步,建立联系
- 首先在原链表每个节点后面都插入一个节点,拷贝前一个值
- 然后和在这个两链表之间插入,与之和这个原链表建立联系
- 第二步,解决random指向
- random的指向是随机的所以我们可以通过前一个节点来访问对应指向,保存copy位置节点
- random的情况可能指向为NULL,正常有节点的情况 看到图中
- 可以得出copy->random = cur->random->next ;这样就指向了之前拷贝在节点后的 7 了,这样拷贝的链表就建立了联系
- 分离链表
- 这个通过尾插的方式来把原链表上复制的几点取下来,所以需要一个头节点和尾节点 为什么?因为题目要求最后要返回一个头节点,而我们要尾插节点
- 看上面的图,这里创建两个 copy的节点和一个after节点,刚好有三个节点还可以顺便还原 原链表
-
只要尾插到后面就行了,链表尾插还不会的可以去看看之前的博客,这里详细的讲了如何尾插--> 单链表的文章
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;
}