Facebook Pixel

2181. Merge Nodes in Between Zeros

MediumLinked ListSimulation
Leetcode Link

Problem Description

You are given a linked list that starts and ends with nodes having value 0. The list contains groups of non-zero integers separated by 0s.

Your task is to merge each group of non-zero values between consecutive 0s into a single node. The value of this new node should be the sum of all the non-zero values in that group. The resulting linked list should not contain any 0s.

For example, if you have a linked list like: 0 → 3 → 1 → 0 → 4 → 5 → 2 → 0, the output should be: 4 → 11

  • The first group between zeros is [3, 1], which sums to 4
  • The second group between zeros is [4, 5, 2], which sums to 11

The algorithm works by:

  1. Starting from the node after the first 0 (since we know the list starts with 0)
  2. Accumulating the sum of values until we encounter another 0
  3. When we hit a 0, we create a new node with the accumulated sum and reset the sum to 0
  4. We continue this process until we reach the end of the list
  5. The dummy node technique is used to simplify handling the head of the result list

The time complexity is O(n) where n is the number of nodes in the original list, and the space complexity is O(1) excluding the output list.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that 0s act as delimiters that divide the linked list into segments. Since we need to merge all nodes between consecutive 0s, we can think of this as a "group and sum" problem.

When traversing a linked list, we naturally process nodes one by one. As we encounter non-zero values, we can accumulate them into a running sum. The moment we hit a 0, we know we've reached the end of a group - this is our signal to create a new node with the accumulated sum and reset our accumulator for the next group.

The pattern becomes clear: we're essentially in one of two states as we traverse:

  1. Accumulating state: When we see non-zero values, add them to our sum
  2. Creating state: When we see a 0, create a new node with the current sum and reset

Since the list starts with a 0, we can skip it and begin processing from the second node. Similarly, since it ends with a 0, our last accumulated sum will naturally be added when we encounter that final 0.

The use of a dummy node is a common linked list technique that eliminates special handling for the head of the result list. Instead of checking whether we're creating the first node or a subsequent node, we can uniformly append all new nodes to tail.next and return dummy.next at the end.

This approach emerges naturally when we think about the problem sequentially - process each element, accumulate when needed, and output when we reach a boundary marker (the 0s).

Learn more about Linked List patterns.

Solution Approach

We implement the solution using a single-pass traversal with the following components:

Data Structures:

  • dummy: A dummy head node that simplifies list construction
  • tail: A pointer that tracks where to append the next merged node
  • s: An accumulator variable to store the running sum
  • cur: A pointer to traverse the original linked list

Algorithm Steps:

  1. Initialize the tracking variables:

    dummy = tail = ListNode()
    s = 0
    cur = head.next  # Skip the first 0

    We create a dummy node and set tail to point to it. Start traversal from head.next since we know the first node is always 0.

  2. Traverse and process each node:

    while cur:
        if cur.val:
            s += cur.val
        else:
            tail.next = ListNode(s)
            tail = tail.next
            s = 0
        cur = cur.next

    For each node in the list:

    • If the value is non-zero (cur.val is truthy), add it to our accumulator s
    • If the value is 0, we've reached the end of a segment:
      • Create a new node with value s
      • Attach it to the result list via tail.next
      • Move tail forward to this new node
      • Reset s to 0 for the next segment
    • Move to the next node with cur = cur.next
  3. Return the result:

    return dummy.next

    Since dummy is a placeholder, the actual result list starts at dummy.next.

Why this works:

  • Every segment between 0s gets accumulated into s
  • When we hit a 0, we know the segment is complete and create the merged node
  • The dummy node pattern ensures we don't need special logic for the first merged node
  • Since the list ends with 0, the last segment is automatically handled when we encounter the final 0

The time complexity is O(n) where n is the number of nodes, as we visit each node exactly once. The space complexity is O(1) for the auxiliary variables (not counting the output list).

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example: 0 → 2 → 3 → 0 → 5 → 0

Initial Setup:

  • Create dummy and tail pointing to a new ListNode
  • Set s = 0 (accumulator for sum)
  • Set cur = head.next (pointing to node with value 2, skipping the first 0)
  • Result list so far: dummy →

Step 1: Process node with value 2

  • cur.val = 2 (non-zero)
  • Add to sum: s = 0 + 2 = 2
  • Move forward: cur now points to node with value 3
  • Result list unchanged: dummy →

Step 2: Process node with value 3

  • cur.val = 3 (non-zero)
  • Add to sum: s = 2 + 3 = 5
  • Move forward: cur now points to node with value 0
  • Result list unchanged: dummy →

Step 3: Process node with value 0

  • cur.val = 0 (delimiter found!)
  • Create new node with accumulated sum: ListNode(5)
  • Attach to result: tail.next = ListNode(5)
  • Move tail forward: tail now points to the new node
  • Reset sum: s = 0
  • Move forward: cur now points to node with value 5
  • Result list now: dummy → 5 →

Step 4: Process node with value 5

  • cur.val = 5 (non-zero)
  • Add to sum: s = 0 + 5 = 5
  • Move forward: cur now points to final node with value 0
  • Result list unchanged: dummy → 5 →

Step 5: Process final node with value 0

  • cur.val = 0 (delimiter found!)
  • Create new node with accumulated sum: ListNode(5)
  • Attach to result: tail.next = ListNode(5)
  • Move tail forward: tail now points to the new node
  • Reset sum: s = 0
  • Move forward: cur becomes None (end of list)
  • Result list now: dummy → 5 → 5 →

Step 6: Return result

  • Return dummy.next, which points to the first real node
  • Final output: 5 → 5

The algorithm successfully merged [2, 3] into 5 and [5] into 5, removing all zeros from the original list.

Solution Implementation

1# Definition for singly-linked list.
2# class ListNode:
3#     def __init__(self, val=0, next=None):
4#         self.val = val
5#         self.next = next
6
7from typing import Optional
8
9class Solution:
10    def mergeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
11        # Create a dummy node to simplify list construction
12        # tail pointer will be used to build the result list
13        dummy_head = ListNode()
14        tail = dummy_head
15      
16        # Initialize sum accumulator for values between zeros
17        current_sum = 0
18      
19        # Start from the node after the first zero
20        current = head.next
21      
22        # Traverse the linked list
23        while current:
24            if current.val != 0:
25                # Accumulate non-zero values
26                current_sum += current.val
27            else:
28                # When we encounter a zero, create a new node with the accumulated sum
29                tail.next = ListNode(current_sum)
30                tail = tail.next
31                # Reset the sum for the next segment
32                current_sum = 0
33          
34            # Move to the next node
35            current = current.next
36      
37        # Return the head of the merged list (skip dummy node)
38        return dummy_head.next
39
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 *     int val;
5 *     ListNode next;
6 *     ListNode() {}
7 *     ListNode(int val) { this.val = val; }
8 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12    public ListNode mergeNodes(ListNode head) {
13        // Create a dummy node to simplify list construction
14        ListNode dummyHead = new ListNode();
15      
16        // Initialize sum accumulator for values between zeros
17        int sum = 0;
18      
19        // Pointer to track the tail of the result list
20        ListNode currentTail = dummyHead;
21      
22        // Iterate through the list starting from the node after the first zero
23        for (ListNode currentNode = head.next; currentNode != null; currentNode = currentNode.next) {
24            if (currentNode.val != 0) {
25                // Accumulate non-zero values
26                sum += currentNode.val;
27            } else {
28                // When encountering a zero, create a new node with the accumulated sum
29                currentTail.next = new ListNode(sum);
30                currentTail = currentTail.next;
31              
32                // Reset sum for the next segment
33                sum = 0;
34            }
35        }
36      
37        // Return the head of the merged list (skip dummy node)
38        return dummyHead.next;
39    }
40}
41
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 *     int val;
5 *     ListNode *next;
6 *     ListNode() : val(0), next(nullptr) {}
7 *     ListNode(int x) : val(x), next(nullptr) {}
8 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13    /**
14     * Merges nodes between zeros in a linked list.
15     * The list starts and ends with 0, with non-zero values in between.
16     * Each segment between consecutive zeros is summed into a single node.
17     * 
18     * @param head: pointer to the head of the linked list
19     * @return: pointer to the head of the merged linked list
20     */
21    ListNode* mergeNodes(ListNode* head) {
22        // Create a dummy node to simplify list construction
23        ListNode* dummyHead = new ListNode();
24        ListNode* currentTail = dummyHead;
25      
26        // Initialize sum accumulator for values between zeros
27        int currentSum = 0;
28      
29        // Start from the node after the first zero
30        for (ListNode* currentNode = head->next; currentNode != nullptr; currentNode = currentNode->next) {
31            if (currentNode->val != 0) {
32                // Accumulate non-zero values
33                currentSum += currentNode->val;
34            } else {
35                // When encountering a zero, create a new node with the accumulated sum
36                currentTail->next = new ListNode(currentSum);
37                currentTail = currentTail->next;
38              
39                // Reset the sum for the next segment
40                currentSum = 0;
41            }
42        }
43      
44        // Return the actual head (skip the dummy node)
45        return dummyHead->next;
46    }
47};
48
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     val: number
5 *     next: ListNode | null
6 *     constructor(val?: number, next?: ListNode | null) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.next = (next===undefined ? null : next)
9 *     }
10 * }
11 */
12
13/**
14 * Merges nodes between zeros in a linked list by summing their values.
15 * The input list starts and ends with 0, with non-zero values in between.
16 * Creates a new list where each node contains the sum of values between consecutive zeros.
17 * 
18 * @param head - The head of the input linked list
19 * @returns The head of the merged linked list
20 */
21function mergeNodes(head: ListNode | null): ListNode | null {
22    // Create a dummy node to simplify list construction
23    const dummyNode: ListNode = new ListNode();
24  
25    // Pointer to track the tail of the result list
26    let tailPointer: ListNode = dummyNode;
27  
28    // Accumulator for summing values between zeros
29    let sum: number = 0;
30  
31    // Iterate through the list starting from the node after head (skip first zero)
32    for (let currentNode: ListNode | null = head!.next; currentNode !== null; currentNode = currentNode.next) {
33        if (currentNode.val !== 0) {
34            // Add non-zero value to the running sum
35            sum += currentNode.val;
36        } else {
37            // When encountering a zero, create a new node with the accumulated sum
38            tailPointer.next = new ListNode(sum);
39            tailPointer = tailPointer.next;
40          
41            // Reset sum for the next segment
42            sum = 0;
43        }
44    }
45  
46    // Return the actual head of the result list (skip dummy node)
47    return dummyNode.next;
48}
49

Time and Space Complexity

Time Complexity: O(n), where n is the total number of nodes in the input linked list.

The algorithm traverses the entire linked list exactly once using a single while loop. For each node in the original list, we perform constant time operations:

  • Checking if cur.val is non-zero: O(1)
  • Adding to sum s: O(1)
  • Creating a new node when encountering a zero: O(1)
  • Moving the pointer cur forward: O(1)

Since we visit each of the n nodes exactly once and perform O(1) operations per node, the overall time complexity is O(n).

Space Complexity: O(m), where m is the number of segments (or the number of zeros minus one) in the input linked list.

However, since in the worst case m can be proportional to n (when zeros and non-zeros alternate), the space complexity can also be expressed as O(n).

The space is used for:

  • Creating new ListNode objects for the result linked list. The number of new nodes created equals the number of segments between zeros.
  • A few constant space variables (dummy, tail, s, cur): O(1)

The algorithm does not use recursion or any additional data structures that scale with input size beyond the output linked list itself, so the space complexity is determined by the size of the output, which is O(n) in the worst case.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Skip the Initial Zero

A common mistake is starting the traversal from head instead of head.next. Since the problem guarantees the list starts with 0, we must skip it to avoid processing it as part of the first group.

Incorrect:

current = head  # Wrong! This includes the first 0
while current:
    if current.val != 0:
        current_sum += current.val
    # ...

Correct:

current = head.next  # Skip the first 0
while current:
    if current.val != 0:
        current_sum += current.val
    # ...

2. Creating Empty Nodes for Consecutive Zeros

If the input contains consecutive zeros (like 0 → 3 → 0 → 0 → 5 → 0), you might accidentally create nodes with value 0 in the result, which violates the requirement that the output should not contain any zeros.

Solution: Add a check before creating a new node:

if current.val == 0:
    if current_sum > 0:  # Only create node if we have accumulated values
        tail.next = ListNode(current_sum)
        tail = tail.next
    current_sum = 0

3. Not Handling Edge Cases

The code might fail on edge cases like:

  • A list with only zeros: 0 → 0 → 0
  • A list with a single group: 0 → 5 → 0

Solution: The current implementation handles these correctly, but always test with:

# Test edge cases
# Input: 0 → 0 → 0 → 0
# Expected Output: None or empty list

# Input: 0 → 5 → 0
# Expected Output: 5

4. Memory Management with the Dummy Node

Forgetting to return dummy.next instead of dummy itself. The dummy node is just a placeholder and should not be part of the final result.

Incorrect:

return dummy_head  # Wrong! This includes the dummy node with value 0

Correct:

return dummy_head.next  # Correct! Skip the dummy node
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More