Merge Nodes Between Zeros — In-Place Pointer Walk Explained
Advertisement
Problem Statement
LeetCode 2181 — Merge Nodes in Between Zeros Difficulty: Medium | Pattern: Pointer Walk + Accumulate
You are given the head of a linked list which contains a series of integers separated by zeros. The beginning and end of the linked list will have Node.val == 0. For every two consecutive zeros, merge all the nodes lying between them into a single node whose value is the sum of all merged nodes. The modified list should not contain any zeros.
Constraints:
- Number of nodes:
3 <= n <= 2 * 10^5 0 <= Node.val <= 1000- The first and last node values are
0. - There are no two consecutive zero-value nodes between the first and last zeros.
Example 1:
Input: [0, 3, 1, 0, 4, 5, 2, 0]
Output: [4, 11]
Explanation:
Segment [3, 1] between first pair of zeros → sum = 4
Segment [4, 5, 2] between second pair of zeros → sum = 11Example 2:
Input: [0, 1, 0, 3, 0, 2, 2, 0]
Output: [1, 3, 4]Why This Problem Matters
This problem tests a clean and practical skill: accumulating values between delimiters in a sequence and producing a condensed output. The linked list format makes it slightly trickier than the array version because you cannot pre-allocate an output size — you must reuse existing nodes in-place.
The pattern of "accumulate until delimiter, then emit" appears in stream processing, log parsing, and data compression algorithms. In an interview at Amazon or during a LinkedIn phone screen, this type of simulation problem is used to test code clarity — can you write a clean, correct loop that handles the accumulation and resetting without introducing bugs?
What makes the in-place approach interesting is the node reuse trick: instead of allocating a new output list, you repurpose existing zero-value nodes as the containers for segment sums. This achieves O(1) extra space beyond the output, making the solution both time- and space-efficient.
The problem also has a conceptual connection to "Run-Length Encoding" — you are essentially compressing runs of values between delimiters into single summary nodes.
The Core Insight
The key observation: the list always starts with a zero and ends with a zero. Every non-zero segment lies strictly between two zeros. You want to replace each such segment with a single node holding the segment sum.
In-place approach using node reuse:
- Skip the leading zero. Let
modifypoint to the dummy/first zero node where you will write sums. - Walk
currthrough the list. Accumulate non-zero values intototal. - When you hit a zero (end of segment): write
totalintomodify, advancemodifyto the current zero node, resettotal = 0. - After the loop, terminate the list at
modify.
This reuses the zero nodes as output nodes, which is O(1) space.
Alternative two-pointer approach:
Maintain a write pointer and a read pointer. When read hits zero, update write.val = total and advance write. This is equivalent.
Visual Dry Run
Input: [0, 3, 1, 0, 4, 5, 2, 0]
| curr.val | action | total | modify.val |
|---|---|---|---|
| 0 (start) | skip leading zero; curr = node(3) | 0 | — |
| 3 | total += 3 | 3 | |
| 1 | total += 1 | 4 | |
| 0 | write 4 to modify; advance modify; reset total | 0 | modify.val=4 |
| 4 | total += 4 | 4 | |
| 5 | total += 5 | 9 | |
| 2 | total += 2 | 11 | |
| 0 | this is the final 0; write 11 to modify; advance modify; modify.next=null | 0 | modify.val=11 |
Output: modify.next = null → list is [4, 11].
Solution (Optimal)
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def mergeNodes(head: Optional[ListNode]) -> Optional[ListNode]:
# Skip the leading zero
curr = head.next
# modify points to the node where we write the next segment sum
modify = head
total = 0
while curr:
if curr.val != 0:
total += curr.val
else:
# End of segment: write sum, advance modify, reset
modify.val = total
modify.next = curr # tentatively point to this zero
modify = modify.next
total = 0
curr = curr.next
# Terminate the output list
modify.next = None
return headfunction mergeNodes(head) {
// Skip the leading zero
let curr = head.next;
let modify = head; // output write pointer
let total = 0;
while (curr !== null) {
if (curr.val !== 0) {
total += curr.val;
} else {
// End of segment: commit sum, advance modify, reset
modify.val = total;
modify.next = curr;
modify = modify.next;
total = 0;
}
curr = curr.next;
}
// The last modify points to the final zero — terminate there
modify.next = null;
return head;
}Complexity:
| Metric | Value |
|---|---|
| Time | O(n) — single pass through the list |
| Space | O(1) — reuses existing nodes, no extra allocation |
Common Mistakes
- Not skipping the leading zero: The list always starts with a zero delimiter. Start traversal from
head.nextto avoid counting the leading zero as part of a segment. - Forgetting to terminate the output list: After the loop,
modify.nextmay still point to old nodes. Always setmodify.next = nullto properly terminate. - Writing sum before advancing modify: The node reuse pattern requires writing to
modify.valand then advancingmodifyto the current zero node. Mixing up this order corrupts the output. - Handling the trailing zero incorrectly: The trailing zero is the last delimiter. When you hit it, write the last sum and advance modify — then the loop ends. Setting
modify.next = nullterminates the output. - Mutating head.val: Since
modifystarts athead, you are overwritinghead.valfrom 0 to the first segment sum. This is intended — the function returnsheadwhich now holds the first sum.
Interview Tips
- Explain node reuse: "Instead of allocating a new list, I reuse the zero nodes as output containers. This gives O(1) extra space."
- Walk through the loop invariant: "At every point,
modifypoints to the last output node written. When I hit a zero, I writetotaltomodify.val, advancemodify, and resettotal." - Mention the termination: Always talk about the final
modify.next = null. Forgetting this is a common bug that leads to garbage tail nodes in the output. - Offer the array alternative: "A simpler O(n) space approach collects sums in an array and builds a new list. I'll use the in-place approach for O(1) space."
- Test with example 2: The input
[0, 1, 0, 3, 0, 2, 2, 0]has three segments. Walk through it to verify your solution produces[1, 3, 4].
Follow-up Questions
- What if zeros could appear consecutively (meaning empty segments)? Empty segments have sum 0 — you would need to decide whether to include zero-sum nodes or skip them.
- What if the list did not start and end with zeros? You would need sentinel handling. The problem's guarantee of leading and trailing zeros simplifies the logic significantly.
- What if you needed to preserve the original list? Allocate new nodes for each sum rather than reusing existing ones.
- Can you solve it recursively? Yes — process the current segment, recurse for the rest. Space is O(n/k) where k is the average segment length due to the call stack.
- LeetCode 2487 — Remove Nodes From Linked List: A related problem where you filter nodes based on conditions, also using in-place pointer manipulation.
Key Takeaways
- The node reuse technique repurposes existing zero nodes as output containers, achieving O(1) extra space.
- Walk from
head.nextto skip the leading zero delimiter. Accumulate non-zero values intototal. - When you encounter a zero (segment boundary), write
totalintomodify.val, advancemodifyto the current zero, and resettotal. - Always terminate the output list with
modify.next = nullafter the loop to avoid garbage tail nodes. - This problem's pattern — "accumulate between delimiters, emit summary node" — appears in stream processing, log parsing, and compression algorithms.
- Time is O(n) for a single pass; space is O(1) with node reuse, or O(n) with a fresh output list.
Advertisement