Facebook Pixel

1669. Merge In Between Linked Lists

Problem Description

You are given two linked lists: list1 and list2 of sizes n and m respectively.

Your task is to remove a range of nodes from list1 (specifically from the a-th node to the b-th node, inclusive) and replace them with the entire list2.

The nodes in linked lists are 1-indexed, meaning:

  • The a-th node is the node at position a (counting from 1)
  • The b-th node is the node at position b (counting from 1)
  • You need to remove all nodes from position a to position b (inclusive)

After removing these nodes, you should insert all nodes from list2 in their place, maintaining the connections properly.

For example, if list1 is [0,1,2,3,4,5], a = 3, b = 4, and list2 is [1000000,1000001,1000002]:

  • You would remove nodes at positions 3 and 4 (which have values 2 and 3)
  • Insert list2 in their place
  • The result would be [0,1,1000000,1000001,1000002,4,5]

The function should return the head of the modified list1 after performing this merge operation.

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

Intuition

To solve this problem, we need to think about what connections we need to modify in the linked list structure.

The key insight is that we need to identify four critical points:

  1. The node just before position a (let's call it the "pre-removal" node)
  2. The node at position b (the last node to be removed)
  3. The head of list2 (the start of what we're inserting)
  4. The tail of list2 (the end of what we're inserting)

Once we have these points, the operation becomes straightforward:

  • Connect the "pre-removal" node to the head of list2
  • Connect the tail of list2 to the node that comes after position b

To find these positions, we can use two pointers traversing from the head of list1:

  • One pointer p moves a-1 steps to reach the node just before position a
  • Another pointer q moves b steps to reach the node at position b

The reason we move p only a-1 steps is because we need to keep the connection point before the removal range. We need access to this node so we can redirect its next pointer to list2.

For q, we move exactly b steps because we need to know what comes after the last removed node (which is q.next), so we can connect the tail of list2 to it.

Finally, we traverse list2 to find its tail node, then complete the connections. This approach modifies the original list1 in-place by splicing in list2 at the correct position.

Learn more about Linked List patterns.

Solution Approach

The solution uses a two-pointer simulation approach to perform the merge operation. Here's the step-by-step implementation:

Step 1: Initialize two pointers

p = q = list1

Both pointers p and q start at the head of list1. These will help us locate the critical connection points.

Step 2: Position pointer p before the removal range

for _ in range(a - 1):
    p = p.next

Move p forward (a-1) times. After this loop, p points to the node just before the a-th node. This is crucial because we need to modify p.next to point to list2.

Step 3: Position pointer q at the end of the removal range

for _ in range(b):
    q = q.next

Move q forward b times from the head. After this loop, q points to the b-th node (the last node to be removed).

Step 4: Connect the pre-removal node to list2

p.next = list2

This disconnects the nodes from position a to b and starts the connection to list2.

Step 5: Find the tail of list2

while p.next:
    p = p.next

Since p.next now points to list2, we traverse through list2 until we reach its tail node (where p.next becomes None).

Step 6: Complete the connection

p.next = q.next
q.next = None

Connect the tail of list2 to the node that came after the b-th node in the original list1. Setting q.next = None helps clean up the removed segment (though this is optional since those nodes are no longer reachable).

Step 7: Return the modified list

return list1

The head of list1 remains unchanged, so we return it as the result.

Time Complexity: O(a + b + m) where m is the length of list2. We traverse to position a-1, then to position b, and finally through all nodes of list2.

Space Complexity: O(1) as we only use a constant amount of extra space for the pointers.

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 walk through a concrete example to illustrate the solution approach.

Given:

  • list1: 10 → 20 → 30 → 40 → 50
  • list2: 100 → 200
  • a = 2, b = 3 (remove nodes at positions 2 and 3)

Goal: Remove nodes 20 and 30 from list1 and insert list2 in their place.

Step 1: Initialize pointers

p = q = list1
Both p and q point to node 10

Step 2: Position p before removal range (move a-1 = 1 time)

p moves 1 step: p now points to node 10
list1: 1020304050
       p

Step 3: Position q at end of removal range (move b = 3 times)

q moves 3 steps: 102030
list1: 1020304050
       p         q

Step 4: Connect p.next to list2

p.next = list2 (node 100)
Now: 10100200
     p
(Nodes 20 and 30 are disconnected)

Step 5: Find tail of list2

Move p through list2:
p: 10100200
        p     p (final position)

Step 6: Complete the connection

p.next = q.next (node 40)
Final: 101002004050

Result: The modified list is 10 → 100 → 200 → 40 → 50

The nodes at positions 2 and 3 (values 20 and 30) have been successfully replaced with the entire list2 (100 → 200).

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
7class Solution:
8    def mergeInBetween(
9        self, list1: ListNode, a: int, b: int, list2: ListNode
10    ) -> ListNode:
11        """
12        Merge list2 into list1 by removing nodes from index a to b (inclusive)
13        and inserting list2 in their place.
14      
15        Args:
16            list1: The first linked list
17            a: Starting index of nodes to remove (0-indexed)
18            b: Ending index of nodes to remove (0-indexed)
19            list2: The second linked list to insert
20      
21        Returns:
22            The modified list1 with list2 merged in between
23        """
24        # Initialize two pointers to traverse list1
25        before_removal_node = list1  # Points to node just before index a
26        after_removal_node = list1   # Points to node at index b
27      
28        # Move before_removal_node to the node just before index a
29        # (a-1 steps forward from the head)
30        for _ in range(a - 1):
31            before_removal_node = before_removal_node.next
32      
33        # Move after_removal_node to the node at index b
34        # (b steps forward from the head)
35        for _ in range(b):
36            after_removal_node = after_removal_node.next
37      
38        # Connect the node before index a to the head of list2
39        before_removal_node.next = list2
40      
41        # Traverse to the end of list2
42        while before_removal_node.next:
43            before_removal_node = before_removal_node.next
44      
45        # Connect the tail of list2 to the node after index b
46        before_removal_node.next = after_removal_node.next
47      
48        # Clean up: disconnect the removed section (optional but good practice)
49        after_removal_node.next = None
50      
51        return list1
52
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 mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
13        // Initialize two pointers to traverse list1
14        ListNode nodeBeforeA = list1;  // Will point to the node just before position 'a'
15        ListNode nodeAtB = list1;       // Will point to the node at position 'b'
16      
17        // Move nodeBeforeA to the node just before position 'a' (a-1 steps)
18        // We need to stop one node before 'a' to maintain the connection
19        while (--a > 0) {
20            nodeBeforeA = nodeBeforeA.next;
21        }
22      
23        // Move nodeAtB to the node at position 'b' (b steps from start)
24        while (b-- > 0) {
25            nodeAtB = nodeAtB.next;
26        }
27      
28        // Connect the node before position 'a' to the head of list2
29        nodeBeforeA.next = list2;
30      
31        // Traverse to the end of list2
32        while (nodeBeforeA.next != null) {
33            nodeBeforeA = nodeBeforeA.next;
34        }
35      
36        // Connect the tail of list2 to the node after position 'b'
37        nodeBeforeA.next = nodeAtB.next;
38      
39        // Clean up: disconnect nodeAtB from the rest of the list (optional but good practice)
40        nodeAtB.next = null;
41      
42        // Return the modified list1
43        return list1;
44    }
45}
46
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    ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
14        // Find the node before position 'a' (where we'll start the merge)
15        ListNode* nodeBeforeA = list1;
16        for (int i = 0; i < a - 1; i++) {
17            nodeBeforeA = nodeBeforeA->next;
18        }
19      
20        // Find the node at position 'b' (last node to be removed)
21        ListNode* nodeAtB = list1;
22        for (int i = 0; i <= b; i++) {
23            nodeAtB = nodeAtB->next;
24        }
25      
26        // Connect the node before position 'a' to the head of list2
27        nodeBeforeA->next = list2;
28      
29        // Traverse to the end of list2
30        ListNode* list2Tail = nodeBeforeA;
31        while (list2Tail->next != nullptr) {
32            list2Tail = list2Tail->next;
33        }
34      
35        // Connect the tail of list2 to the node after position 'b'
36        list2Tail->next = nodeAtB;
37      
38        return list1;
39    }
40};
41
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 list2 into list1 between nodes at positions a and b.
15 * Removes nodes from position a to position b in list1 and inserts list2 in their place.
16 * 
17 * @param list1 - The first linked list
18 * @param a - The starting position (0-indexed) where list2 should be inserted
19 * @param b - The ending position (0-indexed) of nodes to be removed from list1
20 * @param list2 - The second linked list to be inserted
21 * @returns The modified list1 with list2 merged in between
22 */
23function mergeInBetween(
24    list1: ListNode | null,
25    a: number,
26    b: number,
27    list2: ListNode | null,
28): ListNode | null {
29    // Find the node just before position 'a' (where we'll connect list2)
30    let nodeBeforeA: ListNode | null = list1;
31    let nodeAfterB: ListNode | null = list1;
32  
33    // Move nodeBeforeA to the node at position (a-1)
34    while (--a > 0) {
35        nodeBeforeA = nodeBeforeA!.next;
36    }
37  
38    // Move nodeAfterB to the node at position b
39    while (b-- > 0) {
40        nodeAfterB = nodeAfterB!.next;
41    }
42  
43    // Connect the node before position 'a' to the head of list2
44    nodeBeforeA!.next = list2;
45  
46    // Traverse to the end of list2
47    while (nodeBeforeA!.next) {
48        nodeBeforeA = nodeBeforeA!.next;
49    }
50  
51    // Connect the tail of list2 to the node after position 'b'
52    nodeBeforeA!.next = nodeAfterB!.next;
53  
54    // Disconnect the removed portion (optional but good practice for garbage collection)
55    nodeAfterB!.next = null;
56  
57    return list1;
58}
59

Time and Space Complexity

Time Complexity: O(m + n), where m is the length of list1 and n is the length of list2.

The time complexity breaks down as follows:

  • First loop: O(a) iterations to position pointer p at index a-1
  • Second loop: O(b) iterations to position pointer q at index b
  • Third loop: O(n) iterations to traverse through all nodes of list2 to find its tail
  • Since a and b are at most m-1, the worst case for the first two loops is O(m)
  • Total: O(m) + O(n) = O(m + n)

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • Two pointers p and q that reference existing nodes
  • No additional data structures are created
  • The operation modifies the existing linked lists in-place by adjusting pointers

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

Common Pitfalls

1. Off-by-One Error in Pointer Positioning

The Pitfall: The most common mistake is incorrectly positioning the before_removal_node pointer. Many developers mistakenly move it a times instead of a-1 times, which would position it at the a-th node rather than the node before it.

Incorrect Code:

# WRONG: This moves the pointer TO position a, not BEFORE it
for _ in range(a):  # Should be (a-1)
    before_removal_node = before_removal_node.next

Why This Fails:

  • If you move a times, before_removal_node points to the first node that should be removed
  • When you then set before_removal_node.next = list2, you're keeping one node that should have been removed
  • This results in an extra node in the final list

The Solution: Always remember that to insert at position a, you need to access the node at position a-1. Use range(a-1) for the first pointer.

2. Edge Case: When a = 1 (Removing from the Beginning)

The Pitfall: When a = 1, the code tries to move the pointer (a-1) = 0 times, which means before_removal_node stays at the head. This works correctly for the connection but conceptually might be confusing.

Potential Issue: If someone modifies the code without understanding this edge case, they might add special handling that breaks the solution:

# WRONG: Unnecessary special case handling
if a == 1:
    # Trying to handle first node removal differently
    temp = list1
    for _ in range(b):
        temp = temp.next
    list1 = list2  # This doesn't work! We lose reference to return correct head

The Solution: Trust the algorithm - when a = 1:

  • before_removal_node stays at the head (moved 0 times)
  • after_removal_node moves to position b
  • Setting before_removal_node.next = list2 correctly removes nodes 1 through b
  • The original head is preserved and returned correctly

3. Not Handling Empty list2

The Pitfall: If list2 is empty (null), the traversal loop while before_removal_node.next: won't execute, and before_removal_node remains pointing to the node before position a.

Problematic Scenario:

# If list2 is None or empty:
before_removal_node.next = list2  # Sets to None
while before_removal_node.next:   # Loop doesn't execute
    before_removal_node = before_removal_node.next
before_removal_node.next = after_removal_node.next  # Still works correctly!

Why It Actually Works: The code handles this gracefully because when list2 is None, the connection still works properly - we're essentially just removing nodes without inserting anything.

Potential Enhancement (if needed):

if list2:  # Only traverse if list2 exists
    before_removal_node.next = list2
    while before_removal_node.next:
        before_removal_node = before_removal_node.next
else:
    # Direct connection when list2 is empty
    pass  # The next line handles it
before_removal_node.next = after_removal_node.next

4. Confusion with 0-indexed vs 1-indexed Positions

The Pitfall: The problem statement uses 1-indexed positions (nodes are counted starting from 1), but the implementation treats a and b as if they follow this convention. Some developers might get confused and try to adjust for 0-indexing.

Wrong Adjustment:

# WRONG: Don't adjust if the problem already uses 1-indexing
for _ in range(a - 2):  # Incorrectly subtracting 2
    before_removal_node = before_removal_node.next

The Solution: Carefully read whether the problem uses 0-indexing or 1-indexing. In this case:

  • Position 1 = first node
  • Position 2 = second node
  • To get to the node before position a, move (a-1) times from the head
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More