Leetcode 94 合并零之间的节点

1 题目

2181. 合并零之间的节点

给你一个链表的头节点 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 ;
    }
};

代码核心错误分析

  1. 初始求和包含了开头的 0cur 初始指向 head(值为 0),第一次循环就把这个 0 加入了 sum,导致第一个求和结果多了开头的 0。
  2. 指针移动逻辑缺失 :遇到 0 后,没有跳过当前的 0 节点,也没有更新 cur 到下一个区间的起始位置,导致死循环或节点链错乱。
  3. 节点赋值和指针指向错误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;
    }
};

代码关键解释

  1. 跳过开头的 0cur 初始化为 head->next,直接跳过链表开头的 0,避免求和包含无效的 0。
  2. 累加与创建新节点:遍历过程中,非 0 节点累加值;遇到 0 时,用累加和创建新节点,并将其接入新链表。
  3. 虚拟头节点(dummy) :简化新链表的头节点处理(无需判断第一个节点是否为空),最终返回 dummy->next 即可。
  4. 指针移动 :每次循环都将 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) 会在堆上创建一个新节点,值为 sumnext 默认为 nullptr
  • 思想层面:算法题的核心是「逻辑正确」,语法只是载体 ------ 哪怕你暂时记不住构造函数的写法,只要能想清楚「需要一个节点存储 sum,然后把这个节点接到链表末尾」,语法可以通过刷题慢慢积累。
4. 「sum 累加 / 复位」的条件判断逻辑

纠结「判断 cur 还是 cur->next 的 val 是否为 0」,这是链表遍历的「边界条件」问题:

  • 正确逻辑:累加的是「当前节点 cur 的 val」,复位的触发条件是「当前节点 cur 的 val 为 0」(因为 0 是区间分隔符);
  • 错误逻辑(你最初的代码):判断 cur->next->val != 0 会导致两个问题:
    1. 漏加最后一个非 0 节点(比如区间最后一个节点是 1,其 next 是 0,此时不会执行 sum+=1);
    2. 当 cur 是最后一个节点(val=0)时,cur->next 是 nullptr,访问 cur->next->val 会空指针崩溃;
  • 关键原则:遍历链表时,只访问当前节点 cur 的属性,除非能确定 cur->next 不为 nullptr

总结(结合你的反思的核心收获)

  1. 数据结构认知:题目中链表的「数组式输入」是可视化表示,实际操作的是指针,返回的「head」是新链表的头节点,而非原链表的头节点;
  2. 解题策略:链表题优先选择「新建链表(dummy 节点)」,逻辑更简单,减少指针操作错误;
  3. 条件判断:sum 累加判断「当前节点是否非 0」,复位判断「当前节点是否为 0」,避免访问 nullptr 的 next 属性。
相关推荐
tobias.b1 天前
408真题解析-2009-3-数据结构-树-遍历方式
数据结构·计算机考研·408真题
KingRumn1 天前
DBUS源码剖析之DBusMessage消息头
linux·服务器·算法
WaWaJie_Ngen1 天前
【操作系统】第四章---存储器管理
数据结构·算法
sayang_shao1 天前
C++ 多线程【笔记】
c++
benben0441 天前
强化学习DQN和Actor-Critic算法
算法
爪哇部落算法小助手1 天前
每日两题day68
算法
编码小哥1 天前
OpenCV角点检测:Harris与ShiTomasi算法
人工智能·opencv·算法
carver w1 天前
MFC基于对话框Radio按钮的使用方法
c++·mfc
k***92161 天前
如何在C++的STL中巧妙运用std::find实现高效查找
java·数据结构·c++