链表 - 代码随想录

链表 - 代码随想录
Mr.L链表
链表刷题记录,基于代码随想录整理。
- 203 移除链表元素(虚拟头结点)
- 707 设计链表(五大操作)
- 206 反转链表(双指针 / 递归)
- 24 两两交换链表中的节点(虚拟头结点 + 模拟)
- 19 删除链表的倒数第 N 个结点(双指针)
- 160 链表相交(对齐尾部 + 双指针)
- 142 环形链表 II(快慢指针 + 数学推导)
- 虚拟头结点:203、707、24
- 双指针:19、160
- 快慢指针:142
- 反转:206(双指针 / 递归)
203. 移除链表元素
问题描述
给你一个链表的头节点
head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回 新的头节点 。
示例 1:

1 | 输入:head = [1,2,6,3,4,5,6], val = 6 |
示例 2:
1 | 输入:head = [], val = 1 |
示例 3:
1 | 输入:head = [7,7,7,7], val = 7 |
提示:
- 列表中的节点数目在范围
[0, 104]内 1 <= Node.val <= 500 <= val <= 50
思路
这里主要两种方式:
直接使用原来的链表来进行删除操作。
- 局限:当删除头节点时,需要单独定义一段逻辑,不统一

设置一个虚拟头结点在进行删除操作。(推荐!!)
return头结点的时候,别忘了return dummyNode->next;

注:使用C++来做leetcode,如果移除一个节点之后,没有手动在内存中删除这个节点,leetcode依然也是可以通过的,只不过,内存使用的空间大一些而已,但建议依然要养成手动清理内存的习惯。
代码
1 | /** |
- 时间复杂度:O(n)
- 空间复杂度:O(1)
707. 设计链表
问题描述
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val和next。val是当前节点的值,next是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。实现
MyLinkedList类:
MyLinkedList()初始化MyLinkedList对象。int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1。void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。
示例:
1 | 输入 |
提示:
0 <= index, val <= 1000- 请不要使用内置的 LinkedList 库。
- 调用
get、addAtHead、addAtTail、addAtIndex和deleteAtIndex的次数不超过2000。
代码
1 | class MyLinkedList { |
- 时间复杂度:涉及
index的相关操作为 O(index),其余为 O(1) - 空间复杂度:O(n)
206. 反转链表
问题描述
给你单链表的头节点
head,请你反转链表,并返回反转后的链表。
示例 1:

1 | 输入:head = [1,2,3,4,5] |
示例 2:

1 | 输入:head = [1,2] |
示例 3:
1 | 输入:head = [] |
提示:
- 链表中节点的数目范围是
[0, 5000] -5000 <= Node.val <= 5000
进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
思路
双指针法
只需改变链表的next指针的指向,就可将链表反转,而不用重新定义一个新的链表,如图所示:

之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
- 定义一个
cur指针,指向头结点; - 定义一个
pre指针,初始化为null; - 将
cur->next节点用tmp指针保存,然后将cur->next指向pre,此时已反转第一个节点; - 接下来,继续移动
pre和cur指针; - 最终,
cur指针指向了null,循环结束,此时pre指向新的头结点,return pre。
递归法
其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。
采用递归法时,需关注:递归结束条件、返回类型、输入参数。
代码
1 | /** |
- 时间复杂度:O(n)
- 空间复杂度:O(1)
1 | class Solution { |
- 时间复杂度:O(n),要递归处理链表的每个节点
- 空间复杂度:O(n),递归调用了 n 层栈空间
24. 两两交换链表中的节点
问题描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:

1 | 输入:head = [1,2,3,4] |
示例 2:
1 | 输入:head = [] |
示例 3:
1 | 输入:head = [1] |
提示:
- 链表中节点的数目在范围
[0, 100]内 0 <= Node.val <= 100
思路
一定要动手,不要眼高手低!一定要画图,不画图,操作多个指针容易乱,要了解操作的先后顺序!

两两交换操作步骤
以 pre → A → B → C 为例,目标是交换 A 和 B:
pre->next = B(pre 指向 B)B->next = A(B 指向 A,即原来 A 的位置)A->next = C(A 指向 B 原来的下一个节点 C)- 移动
pre = A,cur = C,继续下一对交换
代码
1 | /** |
- 时间复杂度:O(n)
- 空间复杂度:O(1)
19. 删除链表的倒数第 N 个结点
问题描述
给你一个链表,删除链表的倒数第
n个结点,并且返回链表的头结点。
示例 1:

1 | 输入:head = [1,2,3,4,5], n = 2 |
示例 2:
1 | 输入:head = [1], n = 1 |
示例 3:
1 | 输入:head = [1,2], n = 1 |
提示:
- 链表中结点的数目为
sz 1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz
进阶: 你能尝试使用一趟扫描实现吗?
思路
双指针的经典应用: 若要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
fast=head,slow=dummyhead;- 注: fast相对于
dummyhead先走n + 1步,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点;
一定要动手模拟一下!!!!
代码
1 | /** |
- 时间复杂度:O(n)
- 空间复杂度:O(1)
160. 链表相交
问题描述
给你两个单链表的头节点
headA和headB,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
1 | 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 |
示例 2:
1 | 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 |
示例 3:
1 | 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 |
提示:
listA中节点数目为mlistB中节点数目为n0 <= m, n <= 3 * 1041 <= Node.val <= 1050 <= skipA <= m0 <= skipB <= n- 如果
listA和listB没有交点,intersectVal为0 - 如果
listA和listB有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]
进阶: 你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?
思路
简单来说,就是求两个链表交点节点的指针 是否相等,交点不是数值相等;
两个链表若相交,那么剩余的节点数一定是相同的;
- 求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置;
- 比较curA和curB是否相同,如果不相同,同时向后移动curA和curB;
- 如果遇到curA == curB,则找到交点;否则循环退出返回空指针。

代码
1 | /** |
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
142. 环形链表 II
问题描述
给定一个链表的头节点
head,返回链表开始入环的第一个节点。 如果链表无环,则返回null。如果链表中有某个节点,可以通过连续跟踪
next指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos是-1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:

1 | 输入:head = [3,2,0,-4], pos = 1 |
示例 2:

1 | 输入:head = [1,2], pos = 0 |
示例 3:

1 | 输入:head = [1], pos = -1 |
提示:
- 链表中节点的数目范围在范围
[0, 104]内 -105 <= Node.val <= 105pos的值为-1或者链表中的一个有效索引
进阶: 你是否可以使用 O(1) 空间解决此题?
思路
主要考察两知识点:
- 判断链表是否环
- 如果有环,如何找到这个环的入口
判断链表是否有环
快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
- 相对于
slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

如何找到这个环的入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 所以要找环形的入口,就要求解x,因为x表示 头结点到 环形入口节点的的距离。
环入口推导过程
(x + y) * 2 = x + y + n (y + z)(fast指针走过的节点数 =slow指针走过的节点数 * 2)- 化简:
x = (n - 1) (y + z) + z - 从头结点出发一个指针
index1,从相遇节点 也出发一个指针index2,两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。index2指针在环里多转了(n-1)圈。

代码
1 | /** |
- 时间复杂度:
O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个指针走的次数也小于链表长度,总体为走的次数小于2n - 空间复杂度:
O(1)
总结
虚拟头结点: 统一头结点和其他节点的处理情况,避免对头结点的单独判断。
设计链表常见的五个操作: 获取第 index 个节点的值、在头部/尾部/指定位置插入节点、删除指定位置的节点。
双指针法: 快慢指针先行 n 步实现倒数定位;长短链表对齐实现相交检测。
快慢指针: 判环 + 数学推导求环入口。
反转链表: 双指针迭代与递归两种写法,核心都是逐节点翻转 next 指向。
一定要动手模拟!!





