不能直接拼接,因两链表元素通常交错,须逐节点比较;需用dummy哨兵头节点和三个指针实现安全合并,避免空指针与死循环。

合并两个已排序链表时,为什么不能直接拼接?
因为拼接只适用于一个链表整体小于另一个的极端情况;实际中两链表元素交错,必须逐节点比较。核心是维护三个指针:l1、l2 指向待比对节点,dummy 哨兵头节点用于简化边界处理。
常见错误是手动处理头节点导致空指针解引用,或忘记移动某条链的指针造成死循环。正确做法统一用 dummy->next 衔接,每次选较小值后立即推进对应链表指针:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy(0);
ListNode* tail = &dummy;
while (l1 && l2) {
if (l1->val 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;
}
自底向上归并排序:如何避免递归栈溢出?
链表不支持 O(1) 随机访问,递归实现 mergeSort 易触发栈溢出(尤其百万级节点)。更稳妥的是自底向上迭代法:从子长度 1 开始,两两合并,子长度翻倍直至覆盖全长。
关键难点在于切分链表——需用快慢指针找中点,但迭代法中要精确截断前半段尾部(设 prev->next = nullptr),否则合并逻辑错乱:
- 每次合并前,先用计数器走
len步确定第一段终点,再走len步确定第二段终点 - 合并后需记录新链表尾,作为下一次合并的起点,不能依赖原头节点
-
len初始为 1,每次乘 2,但最后一次可能不足len,需检查curr是否为空再进入合并分支
std::list 的 merge() 能直接用吗?
可以,但仅限于两个已排序的 std::list 容器。它就地修改调用方容器,将参数列表合并进来,并清空参数列表。不是通用排序接口,也不能替代手写归并排序逻辑。
使用限制明显:
- 要求双方都已升序排列,否则行为未定义
- 只接受同类型
std::list,不支持原始指针链表或自定义节点 - 时间复杂度 O(n+m),但会破坏原
list2,其迭代器失效且大小变为 0
示例:list1.merge(list2); —— 执行后 list2 为空,list1 包含全部有序元素。
性能差异:链表归并与数组归并的关键区别
链表归并省去了数组归并中的「拷贝回填」步骤,合并过程天然就地完成;但找中点(O(n))和切分(遍历计数)带来额外常数开销。实测在 10⁵ 节点量级,自底向上迭代法比递归快约 15–20%,主因是避免了函数调用与栈帧管理。
真正容易被忽略的是内存局部性:链表节点分散分配,CPU 缓存命中率远低于数组,即使算法步数相同,实际耗时可能翻倍。若数据允许,优先考虑转成 std::vector 排序再重建链表——尤其当链表构建本身非核心逻辑时。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











