1 题目
给你一个链表的头节点 head ,该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0 。
对于每两个相邻的 0 ,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0 移除,修改后的链表不应该含有任何 0 。
返回修改后链表的头节点 head 。
示例 1:

输入:head = [0,3,1,0,4,5,2,0]
输出:[4,11]
解释:
上图表示输入的链表。修改后的链表包含:
- 标记为绿色的节点之和:3 + 1 = 4
- 标记为红色的节点之和:4 + 5 + 2 = 11
示例 2:

输入:head = [0,1,0,3,0,2,2,0]
输出:[1,3,4]
解释:
上图表示输入的链表。修改后的链表包含:
- 标记为绿色的节点之和:1 = 1
- 标记为红色的节点之和:3 = 3
- 标记为黄色的节点之和:2 + 2 = 4
提示:
- 列表中的节点数目在范围
[3, 2 * 105]内 0 <= Node.val <= 1000- 不 存在连续两个
Node.val == 0的节点 - 链表的 开端 和 末尾 节点都满足
Node.val == 0
2 代码实现
思考
开端和末尾都是0 ,那么其实我的想法就是,答案输出是数组,然后我定义一个vector<int> res数组,在遇到0之前都一直计数,如果遇到0了就把当前sum加入res数组,并且sum置0,重新开始。
主要是考察遍历链表吧?输入的head也是数组?思考......不对,返回的是链表的头节点,其实是原地修改链表,删除多余结点,0之后插入新的链表结点,val值是自己计算的!也就是sum!
自己乱写的代码
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeNodes(ListNode* head) {
ListNode* cur = head ;
ListNode* prev = head ;
int sum = 0;
while (cur != nullptr && cur -> next != nullptr ){
sum += cur -> val ;
if (cur -> next -> val != 0 ){
cur = cur -> next ;
}else {
ListNode* Node = cur ;
Node ->val = sum ;
Node -> next = cur -> next ;
prev -> next = Node ;
prev = Node ;
sum = 0 ;
}
}
return head ;
}
};
代码核心错误分析
- 初始求和包含了开头的 0 :
cur初始指向head(值为 0),第一次循环就把这个 0 加入了sum,导致第一个求和结果多了开头的 0。 - 指针移动逻辑缺失 :遇到 0 后,没有跳过当前的 0 节点,也没有更新
cur到下一个区间的起始位置,导致死循环或节点链错乱。 - 节点赋值和指针指向错误 :
Node = cur只是复用了原节点,但没有正确截断后续节点,也没有处理末尾的 0,最终返回的head还是包含开头的 0。
修正后的完整代码
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeNodes(ListNode* head) {
// 跳过开头的 0(题目保证开头是 0)
ListNode* cur = head->next;
// 用于构建新链表的前驱节点
ListNode* dummy = new ListNode(0);
ListNode* prev = dummy;
int sum = 0;
while (cur != nullptr) {
if (cur->val != 0) {
// 非 0 节点,累加值
sum += cur->val;
} else {
// 遇到 0,说明当前区间求和完成
// 创建新节点存储求和结果
prev->next = new ListNode(sum);
// 前驱节点移动到新节点
prev = prev->next;
// 重置求和值
sum = 0;
}
// 无论是否是 0,都移动到下一个节点
cur = cur->next;
}
// 返回新链表的头节点(跳过 dummy 节点)
return dummy->next;
}
};
代码关键解释
- 跳过开头的 0 :
cur初始化为head->next,直接跳过链表开头的 0,避免求和包含无效的 0。 - 累加与创建新节点:遍历过程中,非 0 节点累加值;遇到 0 时,用累加和创建新节点,并将其接入新链表。
- 虚拟头节点(dummy) :简化新链表的头节点处理(无需判断第一个节点是否为空),最终返回
dummy->next即可。 - 指针移动 :每次循环都将
cur移动到下一个节点,确保遍历完整链表,不会遗漏或死循环。
通过的代码
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeNodes(ListNode* head) {
ListNode *cur = head -> next ;
ListNode* dummy = new ListNode(0);
ListNode* prev = dummy ;
int sum = 0;
while (cur != nullptr){
if (cur -> val != 0){
sum += cur -> val ;
}else {
prev -> next = new ListNode(sum);
prev = prev -> next ;
sum = 0;
}
cur = cur -> next ;
}
return dummy -> next;
}
};
3 小结
反思
1.一开始连数据结构都没存储对,错误百出的代码没有贴上去,怎么会输出是数组呢,明明是链表。
2.后来自己乱写的代码逻辑很混乱,输出的链表其实是我自己再维护的一个,而不是希望在原来的链表里面再修改,但是不是说返回head结点吗?奇怪,难道head结点是一个通用的名字而不是特指原来题目里面的head,当然吧,因为head(特指的)对应val是0,输出的没有0 ,而是我自己有维护一个dummy哑头结点。
3.其实我看了题解才知道可以new Listnode(val),我不知道这样创建的办法,语法有点生疏,但是其实算法题也许是见一种语法会一种就好了,语法不影响算法思想。
4.sum += 的逻辑一开始我写的很乱,要弄清楚判断的终止(sum复位0)条件到底是cur -> next 还是cur 的val对应是0。而且在判断外的条件当然是cur一直往下走。
针对反思的补充解析
1. 「输出是数组」→「返回链表头节点」的认知偏差
一开始误以为要输出数组,这是对题目「返回值类型」的误解,也是新手常见问题:
- 题目里
head = [0,3,1,0,4,5,2,0]只是输入的可视化表示,实际传入函数的是「链表头节点指针」,不是数组; - 要求返回
head不是指「原链表的头节点」,而是「修改后新链表的头节点」------ 原链表头节点值为 0,必然要被舍弃,所以用dummy虚拟头节点构建新链表后,返回dummy->next才是正确的新头节点。
2. 「原地修改链表」vs「新建链表」的选择
最初想「原地修改原链表」,但实际解题中:
- 原地修改需要处理「删除 0 节点、复用非 0 节点、截断多余节点」等复杂指针操作,容易出错(比如你最初的代码就是因为指针没跳 0 导致逻辑混乱);
- 新建链表(用
dummy+ 新节点)更简洁,逻辑清晰,且时间复杂度都是 O (n),空间复杂度虽然多了新链表,但算法题中只要不超出题目限制,「可读性优先」是更优选择; - 题目只要求「返回修改后的头节点」,不强制要求「原地修改」,所以新建链表是完全符合题意的。
3. 「new ListNode (sum)」的语法与思想
提到对 new ListNode(sum) 语法生疏,这其实是 C++ 链表题的基础操作:
- 语法层面:
ListNode(int x)是结构体的构造函数,new ListNode(sum)会在堆上创建一个新节点,值为sum,next默认为nullptr; - 思想层面:算法题的核心是「逻辑正确」,语法只是载体 ------ 哪怕你暂时记不住构造函数的写法,只要能想清楚「需要一个节点存储 sum,然后把这个节点接到链表末尾」,语法可以通过刷题慢慢积累。
4. 「sum 累加 / 复位」的条件判断逻辑
纠结「判断 cur 还是 cur->next 的 val 是否为 0」,这是链表遍历的「边界条件」问题:
- 正确逻辑:累加的是「当前节点 cur 的 val」,复位的触发条件是「当前节点 cur 的 val 为 0」(因为 0 是区间分隔符);
- 错误逻辑(你最初的代码):判断
cur->next->val != 0会导致两个问题:- 漏加最后一个非 0 节点(比如区间最后一个节点是 1,其 next 是 0,此时不会执行 sum+=1);
- 当 cur 是最后一个节点(val=0)时,
cur->next是 nullptr,访问cur->next->val会空指针崩溃;
- 关键原则:遍历链表时,只访问当前节点 cur 的属性,除非能确定 cur->next 不为 nullptr。
总结(结合你的反思的核心收获)
- 数据结构认知:题目中链表的「数组式输入」是可视化表示,实际操作的是指针,返回的「head」是新链表的头节点,而非原链表的头节点;
- 解题策略:链表题优先选择「新建链表(dummy 节点)」,逻辑更简单,减少指针操作错误;
- 条件判断:sum 累加判断「当前节点是否非 0」,复位判断「当前节点是否为 0」,避免访问 nullptr 的 next 属性。