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;
}