Merge In Between Linked Lists
MedPrompt
You are given two linked lists: list1 and list2 of sizes n and m respectively.
Remove list1's nodes from the ath node to the bth node, and put list2 in their place.
The blue edges and nodes in the following figure indicate the result:
Build the result list and return its head.
Example 1:
Input: list1 = [10,1,13,6,9,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
Output: [10,1,13,1000000,1000001,1000002,5]
Explanation: We remove the nodes 3 and 4 and put the entire list2 in their place. The blue edges and nodes in the above figure indicate the result.Example 2:
Input: list1 = [0,1,2,3,4,5,6], a = 2, b = 5, list2 = [1000000,1000001,1000002,1000003,1000004]
Output: [0,1,1000000,1000001,1000002,1000003,1000004,6]
Explanation: The blue edges and nodes in the above figure indicate the result.
Constraints:
3 <= list1.length <= 1041 <= a <= b < list1.length - 11 <= list2.length <= 104
Approaches
2 approaches with complexity analysis and trade-offs.
This straightforward approach involves traversing the linked lists multiple times to identify the key nodes for the merge operation. We perform separate traversals to find: the node just before the segment to be removed, the node immediately following the segment, and the tail of the list to be inserted. While easy to conceptualize, it's less efficient due to redundant traversals.
Algorithm
- Create a pointer
nodeBeforeAand traverselist1fora-1steps to find the node at indexa-1. - Create another pointer
nodeAfterBand traverselist1from the head again forb+1steps to find the node at indexb+1. - Create a pointer
tailOfList2and traverselist2to its end. - Connect
nodeBeforeA.nextto the head oflist2. - Connect
tailOfList2.nexttonodeAfterB. - Return the head of
list1.
Walkthrough
The core idea is to break the problem into three independent sub-problems:
- Find the
(a-1)-th node: Traverselist1from the beginning to locate the node at indexa-1. Let's call thisnodeBeforeA. This node'snextpointer will eventually point to the head oflist2. - Find the
(b+1)-th node: Traverselist1again from the beginning to find the node at indexb+1. Let's call thisnodeAfterB. This node will be connected to the tail oflist2. - Find the tail of
list2: Traverselist2from its head to its last node. Let's call thistailOfList2. Once these three nodes are identified, the merging is a simple matter of reassigning pointers:nodeBeforeA.nextis set tolist2, andtailOfList2.nextis set tonodeAfterB.
/** * 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) { // 1. Find the node at index a-1 ListNode nodeBeforeA = list1; for (int i = 0; i < a - 1; i++) { nodeBeforeA = nodeBeforeA.next; } // 2. Find the node at index b+1 ListNode nodeAfterB = list1; for (int i = 0; i < b + 1; i++) { nodeAfterB = nodeAfterB.next; } // 3. Find the tail of list2 ListNode tailOfList2 = list2; while (tailOfList2.next != null) { tailOfList2 = tailOfList2.next; } // 4. Perform the merge nodeBeforeA.next = list2; tailOfList2.next = nodeAfterB; return list1; }}Complexity
Time
O(n + m), where `n` is the length of `list1` and `m` is the length of `list2`. Specifically, the complexity is O(a + b + m). Finding `nodeBeforeA` takes O(a) time, finding `nodeAfterB` takes O(b) time, and finding the tail of `list2` takes O(m) time. Since `a < b < n`, the total time is dominated by O(b + m).
Space
O(1), as we only use a few extra pointers to store the nodes, regardless of the input size.
Trade-offs
Pros
The logic is simple and easy to follow as each major step is handled independently.
Cons
This approach is inefficient because it traverses
list1from the beginning multiple times. The nodes from index 0 toa-1are visited twice, leading to unnecessary operations.
A more efficient method is to find the required connection points in list1 within a single pass. We first traverse to the node before the removal section (a-1). From that point, we continue traversing to find the end of the removal section (b). This avoids restarting the traversal from the head, reducing the total number of steps and making the solution faster.
Algorithm
- Initialize a pointer
startNodetolist1and traversea-1nodes.startNodeis now at indexa-1. - Initialize another pointer
endNodePredecessortostartNode. Traverseb - a + 1more nodes.endNodePredecessoris now at indexb. - Set
startNode.nextto point to the head oflist2. - Traverse
list2to find its tail,tailOfList2. - Set
tailOfList2.nexttoendNodePredecessor.next, which is the node at indexb+1. - Return the head of
list1.
Walkthrough
This optimized approach minimizes the number of node traversals.
- Locate the start of the merge point: We traverse
list1fora-1steps to find the node just before the section to be removed. Let's call thisstartNode. - Locate the end of the merge point: Starting from
startNode, we traverseb - a + 1more steps. Let's call the pointer we use for this traversalendNodePredecessor. After this traversal,endNodePredecessorwill be at nodeb. The portion oflist1that needs to be kept starts atendNodePredecessor.next. - Connect
list1andlist2: We setstartNode.nextto point to the head oflist2. - Find
list2's tail and connect: We traverselist2to find its last node. Then, we connect this last node'snextpointer toendNodePredecessor.next. This way, we traverselist1only up to theb-th node once.
/** * 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) { // 1. Traverse to the (a-1)th node ListNode startNode = list1; for (int i = 0; i < a - 1; i++) { startNode = startNode.next; } // 2. From startNode, find the b-th node ListNode endNodePredecessor = startNode; for (int i = 0; i < b - a + 1; i++) { endNodePredecessor = endNodePredecessor.next; } // 3. Connect the startNode to list2's head startNode.next = list2; // 4. Find the tail of list2 ListNode tailOfList2 = list2; while (tailOfList2.next != null) { tailOfList2 = tailOfList2.next; } // 5. Connect list2's tail to the rest of list1 tailOfList2.next = endNodePredecessor.next; return list1; }}Complexity
Time
O(n + m), where `n` is the length of `list1` and `m` is the length of `list2`. The complexity is specifically O(b + m). We traverse `list1` up to the `b`-th node once (which takes `b` steps in total) and traverse all of `list2` once (which takes `m` steps). This is more efficient than the multiple traversal approach as it avoids redundant steps.
Space
O(1), as we only use a constant amount of extra space for pointers.
Trade-offs
Pros
More efficient in terms of constant factors as it avoids re-traversing the initial part of
list1.The logic is still clear and directly implements the required pointer manipulations.
Cons
No significant cons, as this is the optimal approach for this problem.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int val=0, ListNode next=null) { * this.val = val; * this.next = next; * } * } */ public class Solution { public ListNode MergeInBetween ( ListNode list1 , int a , int b , ListNode list2 ) { ListNode p = list1 , q = list1 ; while (-- a > 0 ) { p = p . next ; } while ( b -- > 0 ) { q = q . next ; } p . next = list2 ; while ( p . next != null ) { p = p . next ; } p . next = q . next ; q . next = null ; 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 ) { ListNode p = list1 , q = list1 ; while (-- a > 0 ) { p = p . next ; } while ( b -- > 0 ) { q = q . next ; } p . next = list2 ; while ( p . next != null ) { p = p . next ; } p . next = q . next ; q . next = null ; 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 ) { auto p = list1 , q = list1 ; while ( -- a ) { p = p -> next ; } while ( b -- ) { q = q -> next ; } p -> next = list2 ; while ( p -> next ) { p = p -> next ; } p -> next = q -> next ; q -> next = nullptr ; return list1 ; } };# 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 : p = q = list1 for _ in range ( a - 1 ): p = p . next for _ in range ( b ): q = q . next p . next = list2 while p . next : p = p . next p . next = q . next q . next = None return list1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.