Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1669. Merge In Between Linked Lists - Leetcode Solution

Problem Description

You're given two singly linked lists, list1 and list2. Additionally, you are provided with two integers, a and b, where 0 <= a <= b < list1.length. Your task is to remove the nodes from index a to index b (inclusive) from list1, and then insert the entire list2 in their place. The result should be a single, well-formed linked list.

  • Only one valid solution exists for each input.
  • Do not reuse or create new nodes; use the given lists and rearrange pointers.
  • All indices are zero-based.
  • Return the head of the merged linked list.

Thought Process

At first glance, this problem looks like a straightforward linked list manipulation. However, the challenge lies in carefully updating the pointers so that the nodes from a to b in list1 are removed and replaced by list2, without losing any nodes or creating cycles.

A brute-force approach might involve copying values or creating new nodes, but the problem specifically asks us to rearrange existing nodes. So, the focus is on:

  • Finding the node just before index a (preA), so we can link it to the head of list2.
  • Finding the node just after index b (postB), so the tail of list2 can point to it.
  • Making sure we do not lose references to any part of the list during pointer updates.

The optimized approach is to traverse list1 only once to find preA and postB, then traverse list2 to find its tail. This ensures the solution is efficient and avoids unnecessary operations.

Solution Approach

Let's break down the steps to solve the problem:

  1. Find the node before index a in list1:
    • Traverse list1 to reach the node at position a - 1. This node is called preA.
  2. Find the node after index b in list1:
    • Continue traversal to reach the node at position b + 1. This node is called postB.
  3. Link preA to the head of list2:
    • Set preA.next = list2.
  4. Find the tail of list2:
    • Traverse list2 until you reach its last node.
  5. Link the tail of list2 to postB:
    • Set tail2.next = postB.
  6. Return the head of the modified list1:
    • Since we only rearranged the pointers, the head remains the same unless a is 0, in which case list2 becomes the new head.

This approach is efficient and ensures that the modifications are done in-place, as required.

Example Walkthrough

Let's walk through an example step by step.

Input:
list1 = 0 -> 1 -> 2 -> 3 -> 4 -> 5
list2 = 1000000 -> 1000001 -> 1000002
a = 3, b = 4

  1. Find preA: Node before index 3 is at index 2 (value: 2).
  2. Find postB: Node after index 4 is at index 5 (value: 5).
  3. Link preA.next to list2: 2 -> 1000000.
  4. Find the tail of list2: 1000002.
  5. Link tail2.next to postB: 1000002 -> 5.
  6. The final list is: 0 -> 1 -> 2 -> 1000000 -> 1000001 -> 1000002 -> 5

At each step, we carefully update the pointers to avoid losing references to any nodes.

Time and Space Complexity

Brute-force approach:
If you tried to copy values or create new nodes, the time and space complexity would be O(N + M), where N and M are the lengths of list1 and list2, due to the need to allocate new nodes.

Optimized approach (in-place pointer manipulation):

  • Time Complexity: O(N + M), where N is the number of nodes up to b in list1 and M is the length of list2. This is because we traverse up to b + 1 in list1 and the entire list2 to find its tail.
  • Space Complexity: O(1), since no additional data structures are used, just pointer manipulation.

Summary

This problem demonstrates the importance of careful pointer manipulation in linked lists. By identifying the nodes before and after the segment to be replaced, and updating their pointers to seamlessly splice list2 into list1, we achieve an efficient, in-place solution. The key insights are to avoid unnecessary node creation and to maintain references to all parts of the list throughout the process. This approach is both elegant and practical for real-world linked list operations.

Code Implementation

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def mergeInBetween(self, list1: ListNode, a: int, b: int, list2: ListNode) -> ListNode:
        # Step 1: Find preA (node before a)
        preA = list1
        for _ in range(a - 1):
            preA = preA.next
        
        # Step 2: Find postB (node after b)
        postB = preA
        for _ in range(b - a + 2):
            postB = postB.next
        
        # Step 3: Connect preA.next to list2
        preA.next = list2
        
        # Step 4: Find tail of list2
        tail2 = list2
        while tail2.next:
            tail2 = tail2.next
        
        # Step 5: Connect tail2.next to postB
        tail2.next = postB
        
        return list1
      
/**
 * 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* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
        // Step 1: Find preA (node before a)
        ListNode* preA = list1;
        for (int i = 0; i < a - 1; ++i) {
            preA = preA->next;
        }
        
        // Step 2: Find postB (node after b)
        ListNode* postB = preA;
        for (int i = 0; i < b - a + 2; ++i) {
            postB = postB->next;
        }
        
        // Step 3: Connect preA->next to list2
        preA->next = list2;
        
        // Step 4: Find tail of list2
        ListNode* tail2 = list2;
        while (tail2->next) {
            tail2 = tail2->next;
        }
        
        // Step 5: Connect tail2->next to postB
        tail2->next = postB;
        
        return list1;
    }
};
      
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        // Step 1: Find preA (node before a)
        ListNode preA = list1;
        for (int i = 0; i < a - 1; ++i) {
            preA = preA.next;
        }
        
        // Step 2: Find postB (node after b)
        ListNode postB = preA;
        for (int i = 0; i < b - a + 2; ++i) {
            postB = postB.next;
        }
        
        // Step 3: Connect preA.next to list2
        preA.next = list2;
        
        // Step 4: Find tail of list2
        ListNode tail2 = list2;
        while (tail2.next != null) {
            tail2 = tail2.next;
        }
        
        // Step 5: Connect tail2.next to postB
        tail2.next = postB;
        
        return list1;
    }
}
      
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {number} a
 * @param {number} b
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeInBetween = function(list1, a, b, list2) {
    // Step 1: Find preA (node before a)
    let preA = list1;
    for (let i = 0; i < a - 1; ++i) {
        preA = preA.next;
    }
    
    // Step 2: Find postB (node after b)
    let postB = preA;
    for (let i = 0; i < b - a + 2; ++i) {
        postB = postB.next;
    }
    
    // Step 3: Connect preA.next to list2
    preA.next = list2;
    
    // Step 4: Find tail of list2
    let tail2 = list2;
    while (tail2.next !== null) {
        tail2 = tail2.next;
    }
    
    // Step 5: Connect tail2.next to postB
    tail2.next = postB;
    
    return list1;
};