Merge Two Sorted Lists

Problem

Given the heads of two sorted singly linked lists list1 and list2, merge the two lists into one sorted list and return the head of the merged list.

  • The number of nodes in both lists is in the range [0, 50]
  • −100 ≤ Node.val ≤ 100
  • Both list1 and list2 are sorted in non-decreasing order

Example

Input: list1 = [1,2,4], list2 = [1,3,4]
Output: [1,1,2,3,4,4]

The algorithm starts with two pointers at the heads of list1 and list2. It compares the current nodes: both have value 1. It chooses the node from list1 first, linking it to the merged list. Then it advances list1's pointer. Next, it compares 2 (list1) and 1 (list2), chooses 1 from list2, and advances list2. This process continues, always linking the smaller current node to the merged list and advancing that pointer. When one list is exhausted, the remaining nodes of the other list are appended directly. The final merged list is sorted and contains all nodes from both lists.

Approach

Straightforward Solution

A naive approach might create a new list and copy values from both lists, but this wastes space and time. Another naive approach is to concatenate and then sort, which is inefficient (O(n log n)).

Core Observation

Merging two sorted lists is fundamentally about repeatedly choosing the smaller current node from either list and linking it to the merged list, preserving sorted order.

Path to Optimal

The key insight is to use two pointers to traverse both lists simultaneously, always linking the smaller node to the merged list and advancing that pointer. This approach exploits the sorted property to merge in a single pass with O(n) time and O(1) auxiliary space.

Optimal Approach

Initialize a dummy node to act as the start of the merged list and a tail pointer to track the last node in the merged list. While both lists have nodes, compare their current values, link the smaller node to tail.next, and advance the corresponding pointer and tail. After the loop, link the remaining nodes from the non-empty list to tail.next. Return dummy.next as the head of the merged list.

Time

O(n + m)

Each node from both lists is visited exactly once and linked into the merged list, resulting in linear time proportional to the total number of nodes.

Space

O(1)

The algorithm uses only a few pointers and does not allocate new nodes or data structures proportional to input size, achieving constant auxiliary space.

Pattern Spotlight

Linked List (Two-Pointer Merge)

When merging two sorted linked lists, use two pointers to traverse both lists simultaneously, always linking the smaller current node to the merged list and advancing that pointer, which guarantees a single linear pass with O(1) auxiliary space.

Solution

1class Solution:
2 def mergeTwoLists(self, list1: ListNode, list2: ListNode) -> ListNode:
3 dummy = ListNode()
4 tail = dummy
5
6 while list1 and list2:
7 if list1.val < list2.val:
8 tail.next = list1
9 list1 = list1.next
10 else:
11 tail.next = list2
12 list2 = list2.next
13 tail = tail.next
14
15 if list1:
16 tail.next = list1
17 elif list2:
18 tail.next = list2
19
20 return dummy.next

Step-by-Step Solution

1

Initialize Dummy Node and Tail Pointer to Build Merged List

3dummy = ListNode()
4tail = dummy

Objective

To create a starting point for the merged list and maintain a pointer to its last node for efficient appending.

Key Insight

Using a dummy node simplifies edge cases by providing a fixed starting node, avoiding special handling for the head of the merged list. The tail pointer always points to the last node in the merged list, enabling O(1) appends as nodes are linked.

Interview Quick-Check

Core Logic

The dummy node acts as a sentinel to simplify list construction, and the tail pointer tracks the end of the merged list for efficient linking.

Common Pitfalls & Bugs

Forgetting to initialize the tail pointer to the dummy node can cause incorrect linking or loss of the merged list head.

2

Iteratively Compare Nodes and Link the Smaller to the Merged List

6while list1 and list2:
7 if list1.val < list2.val:
8 tail.next = list1
9 list1 = list1.next
10 else:
11 tail.next = list2
12 list2 = list2.next
13 tail = tail.next

Objective

To traverse both lists simultaneously, always appending the smaller current node to the merged list and advancing pointers accordingly.

Key Insight

Because both lists are sorted, the smallest node at each step must come from one of the two current nodes. By comparing and linking the smaller node, the merged list remains sorted. Advancing the pointer of the chosen node ensures progress and prevents infinite loops.

Interview Quick-Check

Core Logic

At each iteration, compare the current nodes of both lists, link the smaller node to the merged list, and advance that list's pointer and the tail pointer.

State & Boundaries

The loop continues while both lists have nodes, ensuring all nodes are processed.

Common Pitfalls & Bugs

Failing to advance the tail pointer after linking a node breaks the chain and corrupts the merged list.

3

Append Remaining Nodes from Non-Exhausted List to Complete Merge

15if list1:
16 tail.next = list1
17elif list2:
18 tail.next = list2

Objective

To link any leftover nodes from either list after the other is fully traversed, completing the merged list.

Key Insight

Since the input lists are sorted, the remaining nodes in the non-empty list are already in order and greater than all previously merged nodes. Directly linking them avoids unnecessary comparisons and preserves sorted order.

Interview Quick-Check

Core Logic

After one list is exhausted, append the remaining nodes of the other list directly to the merged list.

Common Pitfalls & Bugs

Neglecting to append the remaining nodes results in an incomplete merged list missing tail elements.

4

Return the Head of the Merged List Skipping Dummy Node

20return dummy.next

Objective

To return the merged list starting from the first real node, excluding the dummy node.

Key Insight

The dummy node is a placeholder and not part of the actual merged list. Returning dummy.next provides the correct head of the merged list constructed during the process.

Interview Quick-Check

Core Logic

Return dummy.next to provide the head of the merged list, excluding the dummy sentinel node.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 8 Critical
tail.next = list1

Link the smaller node (from list1) to the merged list.

Linking the smaller node preserves the sorted order and builds the merged list incrementally.

Line 11 Critical
tail.next = list2

Link the smaller node (from list2) to the merged list.

Linking the smaller node from list2 maintains the sorted order and continues building the merged list.

Line 13 Critical
tail = tail.next

Advance the tail pointer to the newly linked node.

Moving the tail pointer keeps it at the end of the merged list, enabling O(1) appends in subsequent iterations.

Line 20 Critical
return dummy.next

Return the head of the merged list, skipping the dummy node.

Returning dummy.next provides the actual start of the merged list, excluding the placeholder dummy node.

Line 3 Core
dummy = ListNode()

Create a dummy node to serve as the fixed starting point of the merged list.

The dummy node simplifies edge cases by providing a non-null starting node, allowing uniform linking logic without special handling for the head.

Line 9 Core
list1 = list1.next

Advance the pointer in list1 after linking its node.

Advancing the pointer moves forward in list1, ensuring progress and preventing infinite loops.

Line 12 Core
list2 = list2.next

Advance the pointer in list2 after linking its node.

Advancing the pointer in list2 ensures progress and prevents infinite loops.

Line 15 Core
if list1:

If list1 still has nodes, link the remaining nodes to the merged list.

Appending the remaining nodes from list1 preserves sorted order and completes the merged list.

Line 17 Core
elif list2:

If list2 still has nodes, link the remaining nodes to the merged list.

Appending the remaining nodes from list2 preserves sorted order and completes the merged list.

Line 4 Core
tail = dummy

Initialize the tail pointer to the dummy node to track the end of the merged list.

The tail pointer enables efficient appending of nodes by always pointing to the last node in the merged list, avoiding traversal.

Line 7 Core
if list1.val < list2.val:

Compare the current values of list1 and list2 nodes.

The comparison determines which node should be linked next to maintain sorted order in the merged list.

Line 10 Core
else:

Handle the case where list2's current node is smaller or equal.

This else branch ensures the smaller or equal node from list2 is linked, maintaining sorted order.

Line 16 Core
tail.next = list1

Link the remaining nodes of list1 to tail.next.

Directly linking the leftover nodes avoids unnecessary comparisons and maintains correctness.

Line 18 Core
tail.next = list2

Link the remaining nodes of list2 to tail.next.

Directly linking the leftover nodes avoids unnecessary comparisons and maintains correctness.

Line 6 Standard
while list1 and list2:

Start a loop that continues while both list1 and list2 have nodes.

This loop ensures that nodes are merged until at least one list is fully traversed, maintaining progress and correctness.

Test Your Understanding

Why is it safe to link the remaining nodes of one list directly after the other list is exhausted?

Because both input lists are sorted, the remaining nodes in the non-exhausted list are already greater than all nodes linked so far, so appending them preserves the sorted order of the merged list.

Related Problems

Linked Lists pattern

Don't just read it. Drill it.

Reconstruct Merge Two Sorted Lists from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Merge Two Sorted Lists drill

or drill a free problem