单链表作为数据结构中最基础也最重要的线性结构之一,在算法面试和实际开发中都有着广泛应用。今天我将结合自己多年的开发经验,为大家详细解析单链表的六大经典算法问题,包括思路分析、代码实现和常见陷阱。这些内容不仅适合准备面试的同学,对日常开发中的链表操作也有很强的参考价值。
问题描述:给定一个链表头节点和一个整数值,删除链表中所有值等于给定值的节点,返回新的头节点。
核心思路:
三指针法:使用prev、pcur、next三个指针遍历链表
尾插法:创建新链表,将不需要删除的节点尾插到新链表
代码实现关键点:
c复制struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* newHead = NULL, *newTail = NULL;
ListNode* pcur = head;
while(pcur) {
if(pcur->val != val) {
if(!newHead) {
newHead = newTail = pcur;
} else {
newTail->next = pcur;
newTail = pcur;
}
}
pcur = pcur->next;
}
if(newTail) newTail->next = NULL; // 关键步骤
return newHead;
}
常见错误:
问题描述:将单链表的所有节点反转,返回反转后的头节点。
两种经典解法:
头插法:
三指针法(推荐):
三指针法实现细节:
c复制struct ListNode* reverseList(struct ListNode* head) {
if(!head) return head;
ListNode *n1 = NULL, *n2 = head, *n3 = head->next;
while(n2) {
n2->next = n1; // 反转指针
n1 = n2;
n2 = n3;
if(n3) n3 = n3->next; // 边界检查
}
return n1;
}
注意事项:
问题描述:找到单链表的中间节点,如果有两个中间节点则返回第二个。
快慢指针法:
实现代码:
c复制struct ListNode* middleNode(struct ListNode* head) {
ListNode *slow = head, *fast = head;
while(fast && fast->next) { // 关键条件
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
易错点:
问题描述:将两个升序链表合并为一个新的升序链表。
尾插法实现:
优化技巧:
使用哨兵节点(dummy node)简化代码:
c复制struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
ListNode dummy = {0, NULL}; // 哨兵节点
ListNode *tail = &dummy;
while(l1 && l2) {
if(l1->val < l2->val) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
tail->next = l1 ? l1 : l2; // 连接剩余部分
return dummy.next;
}
性能考虑:
问题描述:n个人围成一圈,从第1个人开始报数,数到m的人出局,最后剩下的人的编号。
环形链表解法:
关键实现:
c复制typedef struct ListNode ListNode;
ListNode* createCircle(int n) {
ListNode *head = malloc(sizeof(ListNode));
head->val = 1;
ListNode *tail = head;
for(int i = 2; i <= n; i++) {
tail->next = malloc(sizeof(ListNode));
tail = tail->next;
tail->val = i;
}
tail->next = head; // 成环
return tail; // 返回尾节点便于操作
}
int ysf(int n, int m) {
ListNode *prev = createCircle(n);
ListNode *curr = prev->next;
int count = 1;
while(curr->next != curr) {
if(count == m) {
prev->next = curr->next;
free(curr);
curr = prev->next;
count = 1;
} else {
prev = curr;
curr = curr->next;
count++;
}
}
int res = curr->val;
free(curr);
return res;
}
注意事项:
问题描述:给定一个链表和值x,将所有小于x的节点排在大于等于x的节点之前。
大小链表法:
实现代码:
c复制struct ListNode* partition(struct ListNode* head, int x) {
ListNode lessDummy = {0, NULL}, greaterDummy = {0, NULL};
ListNode *lessTail = &lessDummy, *greaterTail = &greaterDummy;
while(head) {
if(head->val < x) {
lessTail->next = head;
lessTail = lessTail->next;
} else {
greaterTail->next = head;
greaterTail = greaterTail->next;
}
head = head->next;
}
greaterTail->next = NULL; // 避免循环
lessTail->next = greaterDummy.next;
return lessDummy.next;
}
优化点:
链表可以根据三个维度进行分类,组合起来共有8种类型:
单向 vs 双向:
带头节点 vs 不带头节点:
循环 vs 非循环:
单链表:
双向循环链表:
带头单链表:
单链表:
双向链表:
循环链表:
哨兵节点(dummy node)技巧:
快慢指针法:
多指针协同:
尾指针维护:
画图分析法:
指针丢失:
边界条件:
循环引用:
内存管理:
调试建议:
空间换时间:
预处理技巧:
分治策略:
内核链表实现:
内存池管理:
LRU缓存实现:
递归反转链表:
递归遍历:
递归思维:
在实际开发中,链表操作是基本功,建议读者: