代码随想录算法训练营第4天|24. 两两交换链表中的节点 、19.删除链表的倒数第N个节点 、面试题 02.07. 链表相交、142.环形链表II
LeetCode24
2025-01-25 15:05:55 星期六
题目描述:力扣24
文档讲解:代码随想录(programmercarl)24.两两交换链表中的节点
视频讲解:《代码随想录》算法视频公开课:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点
代码随想录视频内容简记
用虚拟头结点的方式来做
梳理

上图中蓝线表示的执行的反转操作,红色序号表示的每一步反转操作导致消失的的指针
-
while
的循环条件 -
反转操作
-
设置两个
tmp
临时的指针为了循环往下遍历
大致代码内容
-
while (cur->next != NULL && cur->next->next != NULL)
,,前一个是在链表有偶数个结点的情况,后一个是链表有奇数个结点的情况。这个条件一定不能写反,因为一旦反过来写,如果cur->next
为空指针,那么cur->next->next
就相当于操作空指针了 -
反转操作
tmp = cur->next;
tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
- 因为为了避免链表结点反转时出现对空指针的操作,所以要设置两个temp指针
LeetCode测试
代码比较好通过
点击查看代码
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
ListNode* tmp;
ListNode* tmp1;
while (cur->next != NULL && cur->next->next != NULL) {
// 交换结点
tmp = cur->next;
tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
// 指针后移
cur = cur->next->next;
}
return dummyHead->next;
}
};
LeetCode19
题目描述:力扣19
文档讲解:代码随想录(programmercarl)19.删除链表的倒数第N个节点
视频讲解:《代码随想录》算法视频公开课:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点
代码随想录视频内容简记
这道题仍然使用的是双指针的思想,一个快指针和一个满指针,和27.移除元素很相近,但是这道题目巧妙的地方在于先让快指针移动n+1
步,之后快慢指针同时移动直到快指针为空。
梳理

-
首先是定义虚拟头结点,以及对快慢指针的定义
-
第一个while循环控制先让快指针移动
n+1
步。如上图,我们确实需要删除slow指针为倒数n=2
的结点,但是就因为如此,才需要移动该位置的前驱结点,来对其进行删除。也就是slow需要少移动一个位置,而fast需要多移动一个位置,移到红框所指的实箭头处。 -
第二个while循环控制再让快慢指针同时移动
-
删除结点
大致代码内容
- 快指针先移动。首先进行
n++
操作,之后进行while (n-- && fast != NULL)
while (n-- && fast != NULL) {
fast = fast->next;
}
- 两个指针同时移动
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
LeetCode测试
代码比较好通过,就是不要总是忘了dummyHead->next = head
,要不然总会报空指针的错误。
点击查看代码
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
dummyHead->next = head;
n++;
while (n-- && fast != NULL) {
fast = fast->next;
}
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete tmp;
return dummyHead->next;
}
};
面试题 02.07. 链表相交
题目描述:力扣面试题 02.07. 链表相交
文档讲解:代码随想录(programmercarl)面试题 02.07. 链表相交
内容梳理
-
这个在王道书上见过,之要判断只要中间有一个指针是相同的,那么其后的部分就一定相交,因为指针所指的地址肯定是一样的。因为链表的长度不一样,所以要先在长链表上遍历两个链表之间长度的差
-
核心还是双指针的思想,一个负责A链表,一个负责B链表
LeetCode测试
这个题还是有几点值得注意的
- 那就是指针相等数值相等。可以看力扣在题目描述中给出的示例1

刚开始我也很疑惑,为什么不是从1开始的。后来看评论区,这是是假设了一种可能,也就是curA指向的1和curB指向的1的地址不一样,但是二者所指向的8的地址是一样的。所以在判断是就要用if (curA == curB)
来直接进行判断,而不是if (curA->val == curB->val)
- 注意这个循环遍历的终止条件的书写,因为题目要求“如果两个链表没有交点,返回 null”,所以while循环中要同时给出两个情况的return
while (curA != NULL) {
if (curA == curB) return curA;
...
}
return NULL;
链表相交完整代码
点击查看代码
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0, lenS;
while (curA != NULL) {
lenA++;
curA = curA->next;
}
while (curB != NULL) {
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if (lenB > lenA) {
swap(lenB, lenA);
swap(curB, curA);
}
lenS = lenA - lenB;
while (lenS-- && curA != NULL) {
curA = curA->next;
}
// while (curA->val != curB->val) {
// curA = curA->next;
// curB = curB->next;
// }
// return curA;
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
LeetCode142
题目描述:力扣142
文档讲解:代码随想录(programmercarl)142.环形链表II
视频讲解:《代码随想录》算法视频公开课:把环形链表讲清楚!| LeetCode:142.环形链表II
代码随想录视频简记
梳理

-
判断是否有环
首先是一个快指针,一个慢指针,如果两个指针能出现相等的情况,那说明这个链表一定有环。
但是如何保证不出现快指针把满指针正好跳过的情况呢?,这里就需要对二者的移动速度进行特殊定义,也就是快指针每次移动两个位置,慢指针每次移动一个位置。这样快指针每次相对于慢指针的移动位数就是1,那么以1个相对位置的距离靠近,就可以保证中间没有跳过。

还有一点就是,注意二者的相遇并不是追及,从动图中可以看出来,快指针中间追上了慢指针,但是仍然会往前走一个距离,而等慢指针再走一个距离。也就是无论是fast还是slow指针,其next
操作必须走完才行
-
确定环的入口
这里需要做一些简单的数学推导
我们假设慢指针首先走过了,而快指针因为在圈内绕过了圈,所以快指针走过了,而因为二者的速度差异,即快指针是慢指针的速度二倍。所以可列出下面的等式
由此,我们便得到了关键的公式。我们可以代入特殊值发现,,也就是说无论快指针在环内转了多少圈,的距离等于圈加上的距离。
那么如果定义一个index1
指针指向两个指针相遇的地方,一个index2
指向头结点,那么两个指针同时移动,最终相等的地方就是环的入口
这里还有一个问题就是K哥讲到的,为什么慢指针走过的距离不能是?,就是为什么慢指针在走完第一圈的时候就一定会被快指针追上呢?

可以看到,慢指针从入口进入,在走完第一圈的时候,快指针会以二倍慢指针的速度超过他,所以一定会在慢指针走完第一圈的时候追上慢指针
大致代码内容
注意代码中仍然是指针与指针相等,并不是指针的val
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
index1 = fast;
index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
还有就是为什么需要while (fast != NULL && fast->next != NULL)
,因为我们需要保证fast = fast->next->next
是有意义的,如果fast->next = NULL
,那么NULL->next
就显然没有意义了。
按照k哥的说法
这么做是为了放心大胆的进行
fast = fast->next->next
LeetCode测试
关于时间复杂度
关于代码的时间复杂度,文档中是这么写的,在快慢指针相遇前,快慢指针的元素的平均操作次数小于链表长度n,所以时间度复杂度为,在相遇之后,index1和index2指针对元素的平均操作操作长度也小于链表长度n,所以时间复杂度也为,最终的时间复杂度就为
环形链表Ⅱ完整代码如下
点击查看代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
ListNode* index1;
ListNode* index2;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
index1 = fast;
index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构