链表是算法中最基础且高频的数据结构之一,核心难点在于指针操作的细节处理(如空指针判断、指针指向更新)。常见考点包括链表的遍历、反转、合并、重排等,需要熟练掌握指针操作的逻辑,避免空指针错误和内存泄漏。本文通过5道经典链表题目,覆盖链表的核心操作与解题技巧。
一、两数相加
题目描述:
两个非负整数以逆序链表形式存储(每个节点存一位数字),返回它们相加后的逆序链表。
示例:
- 输入:
l1 = [2,4,3],l2 = [5,6,4],输出:[7,0,8](对应数字342 + 465 = 807)
解题思路:
模拟手工加法过程,用哑节点简化头节点处理:
- 用
t存储进位,遍历两个链表,累加当前节点值与进位。 - 每个位置的结果为
t % 10,新的进位为t / 10。 - 遍历结束后,若仍有进位则追加新节点。
完整代码:
cpp
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* cur1 = l1, * cur2 = l2;
ListNode* newhead = new ListNode(0);
ListNode* tail = newhead;
int t = 0;
while(cur1 || cur2 || t)
{
if(cur1)
{
t += cur1->val;
cur1 = cur1->next;
}
if(cur2)
{
t += cur2->val;
cur2 = cur2->next;
}
tail->next = new ListNode(t % 10);
tail = tail->next;
t /= 10;
}
tail = newhead->next;
delete newhead;
return tail;
}
};
复杂度分析:
- 时间复杂度:O(max(n,m))O(\max(n,m))O(max(n,m)),
n、m是两个链表的长度,遍历次数为较长链表的长度。 - 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量(结果链表的空间为必要输出,不计入额外复杂度)。
二、两两交换链表中的节点
题目描述:
两两交换链表中的相邻节点(不能修改节点值,只能交换节点),返回交换后的头节点。
示例:
- 输入:
head = [1,2,3,4],输出:[2,1,4,3]
解题思路:
用哑节点作为前驱,每次交换一对节点:
- 定义前驱节点
prev(初始为哑节点)、当前节点cur、下一个节点next、下下个节点nnext。 - 交换
cur和next:prev->next = next,next->next = cur,cur->next = nnext。 - 更新前驱指针为
cur,继续处理下一对节点。
完整代码:
cpp
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next) return head;
ListNode* newhead = new ListNode(0);
newhead->next = head;
ListNode* prev = newhead, *cur = head, *next = cur->next, *nnext = next->next;
while(cur && next)
{
prev->next = next;
next->next = cur;
cur->next = nnext;
prev = cur;
cur = nnext;
if(cur) next = cur->next;
if(next) nnext = next->next;
}
cur = newhead->next;
delete newhead;
return cur;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),遍历链表一次。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
三、重排链表
题目描述:
将链表 L0→L1→...→Ln-1→Ln 重排为 L0→Ln→L1→Ln-1→...(不能修改节点值,只能交换节点)。
示例:
- 输入:
head = [1,2,3,4],输出:[1,4,2,3]
解题思路:
三步实现:找中点→反转后半段→合并前后两段
- 找中点:快慢指针遍历,慢指针最终指向链表中点。
- 反转后半段:从中点的下一个节点开始,反转后半段链表。
- 合并:交替合并前半段和反转后的后半段链表。
完整代码:
cpp
class Solution {
public:
void reorderList(ListNode* head) {
if(!head || !head->next || !head->next->next) return;
// 1. 快慢指针找中点
ListNode* slow = head, *fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
// 2. 反转后半段链表
ListNode* head2 = new ListNode(0);
ListNode* cur = slow->next;
slow->next = nullptr; // 断链
while(cur)
{
ListNode* next = cur->next;
cur->next = head2->next;
head2->next = cur;
cur = next;
}
// 3. 合并前后两段
ListNode* ret = new ListNode(0);
ListNode* tail = ret;
ListNode* cur1 = head, *cur2 = head2->next;
while(cur1)
{
tail->next = cur1;
cur1 = cur1->next;
tail = tail->next;
if(cur2)
{
tail->next = cur2;
cur2 = cur2->next;
tail = tail->next;
}
}
delete head2;
delete ret;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),找中点、反转、合并均为线性时间。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
四、合并K个升序链表
题目描述:
合并 k 个升序链表为一个升序链表。
示例:
- 输入:
lists = [[1,4,5],[1,3,4],[2,6]],输出:[1,1,2,3,4,4,5,6]
解题思路:
用**最小堆(优先队列)**维护当前所有链表的头节点,每次取出最小的节点:
- 定义堆的比较规则(按节点值升序),将所有非空链表的头节点加入堆。
- 取出堆顶节点(最小节点),加入结果链表,若该节点有下一个节点则将其加入堆。
- 重复步骤2直到堆为空。
完整代码:
cpp
class Solution {
struct cmp
{
bool operator()(const ListNode* l1, const ListNode* l2)
{
return l1->val > l2->val;
}
};
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
for(auto x : lists)
if(x) heap.push(x);
ListNode* ret = new ListNode(0);
ListNode* cur = ret;
while(!heap.empty())
{
ListNode* t = heap.top();
heap.pop();
cur->next = t;
cur = t;
if(t->next) heap.push(t->next);
}
cur = ret->next;
delete ret;
return cur;
}
};
复杂度分析:
- 时间复杂度:O(Nlogk)O(N \log k)O(Nlogk),
N是所有链表的总节点数,每个节点入堆/出堆的时间为 O(logk)O(\log k)O(logk)。 - 空间复杂度:O(k)O(k)O(k),堆的大小最多为
k(存储所有链表的头节点)。
五、K个一组翻转链表
题目描述:
每 k 个节点一组翻转链表,最后不足 k 个的节点保持原有顺序。
示例:
- 输入:
head = [1,2,3,4,5], k = 2,输出:[2,1,4,3,5]
解题思路:
先统计节点数确定翻转组数,再逐组翻转:
- 统计链表总节点数,计算需要翻转的组数
n = 总节点数 / k。 - 用哑节点作为前驱,每组翻转
k个节点,更新前驱指针为该组的最后一个节点。 - 处理完所有组后,将剩余不足
k个的节点直接连接到结果链表。
完整代码:
cpp
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* cur = head;
int n = 0;
while(cur)
{
n++;
cur = cur->next;
}
n /= k; // 翻转的组数
ListNode* newhead = new ListNode(0);
ListNode* prev = newhead;
cur = head;
while(n--)
{
ListNode* tmp = cur;
// 翻转当前组的k个节点
for(int i = 0; i < k; i++)
{
ListNode* next = cur->next;
cur->next = prev->next;
prev->next = cur;
cur = next;
}
prev = tmp; // 更新前驱为当前组的最后一个节点
}
prev->next = cur; // 连接剩余节点
cur = newhead->next;
delete newhead;
return cur;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),遍历链表统计节点数,每组翻转均为线性时间。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。