Return the node where two singly linked lists intersect. If the linked lists don't intersect, return null.
Output: Node 8
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Two on-ramps of different lengths feed into the same highway. If two cars leave the start of each ramp at the same speed, the one on the shorter ramp reaches the merge first and pulls ahead. To make them arrive together, give each car a clever route: drive your ramp, then drive the other car's ramp. Now both cars travel the exact same total distance, so they reach the merge point at the same instant.
Let’s first understand what an intersection between two linked lists is.
An intersection occurs when two linked lists converge at a shared node and, from that point onwards, share all subsequent nodes.
Note, this intersection has nothing to do with the values of the nodes.
A naive approach is to use a hash set. We can traverse the first linked list once and store each node in a hash set. Next, we traverse the second linked list until we find the first node that exists in the hash set, signifying the intersection point since it’s the first node shared between the two linked lists. This approach solves the problem linearly, but can we find a solution that uses constant space?
Consider the following example:
Treating these as two separate linked lists can get confusing with the above visualization. Instead, let’s visualize the input as two linked lists to help us think about the problem more clearly. Note that the tail nodes are still shared between the two linked lists, we’re just visualizing them separately:
Notice that this problem is easier to solve if the two linked lists are of equal length. This is because the intersection node can be found at the same position from the heads of both linked lists. In other words, when we iterate through two linked lists of the same length, we’re guaranteed to reach the intersection node at the same time:
Could we somehow replicate this behavior when dealing with linked lists of varying lengths? The key observation is that, while two linked lists ‘list A’ and ‘list B’ may have different lengths, ‘list A → list B’ has the same length as ‘list B → list A’ (where ‘→’ represents the connection of two lists). Conveniently, these combined linked lists also share the same tail nodes:
We’ve now set up a scenario where we have two combined linked lists of the same length, which share the same tail nodes. By traversing these combined linked lists, we’ll eventually reach the intersection node simultaneously on both linked lists (if one exists).
To do this, we can traverse both combined linked lists with two pointers, and stop once the nodes at both pointers are the same. This node would be the intersection node:
If no intersection exists, both pointers will end up stopping at null nodes:
Traversing combined linked lists
An important observation is that to traverse through 'list A → list B', we don't actually need to connect these two linked lists together. Instead, we can traverse 'list A' and, upon reaching its end, continue by traversing 'list B':
This technique allows us to traverse the entire sequence of both linked lists as if they were connected.
from ds import ListNode
def linked_list_intersection(head_A: ListNode, head_B: ListNode) -> ListNode:
ptr_A, ptr_B = head_A, head_B
# Traverse through list A with 'ptr_A' and list B with 'ptr_B' until they meet.
while ptr_A != ptr_B:
# Traverse list A -> list B by first traversing 'ptr_A' and then, upon
# reaching the end of list A, continue the traversal from the head of list B.
ptr_A = ptr_A.next if ptr_A else head_B
# Simultaneously, traverse list B -> list A.
ptr_B = ptr_B.next if ptr_B else head_A
# At this point, 'ptr_A' and 'ptr_B' either point to the intersection node or both
# are null if the lists do not intersect. Return either pointer.
return ptr_AListNode* linkedListIntersection(ListNode* headA, ListNode* headB) {
ListNode* a = headA;
ListNode* b = headB;
while (a != b) { // compare pointers, not values
a = (a != nullptr) ? a->next : headB; // ran off A? jump to B
b = (b != nullptr) ? b->next : headA; // ran off B? jump to A
}
return a; // the shared node, or nullptr
}public ListNode linkedListIntersection(ListNode headA, ListNode headB) {
ListNode a = headA;
ListNode b = headB;
while (a != b) { // compare references, not values
a = (a != null) ? a.next : headB; // ran off A? jump to B
b = (b != null) ? b.next : headA; // ran off B? jump to A
}
return a; // the shared node, or null
}Time complexity: The time complexity of linked_list_intersection is , where and denote the lengths of list A and B, respectively. This is because pointers linearly traverse both linked lists sequentially.
Space complexity: The space complexity is .
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
pointer a pointer b shared tail
| iter | a on | b on | a == b? |
|---|---|---|---|
| 0 | 2 (A) | 5 (B) | no |
| 1 | 3 (A) | 7 (shared) | no |
| 2 | 7 (shared) | 8 (shared) | no |
| 3 | 8 (shared) | ∅ → jump | no |
| 4 | ∅ → jump | 2 (A) | no |
| 5 | 5 (B) | 3 (A) | no |
| 6 | 7 (shared) | 7 (shared) | yes → return 7 |
pointer a pointer b
null.
a == b == null. The loop ends and we return null. ✅
| iter | a on | b on | a == b? |
|---|---|---|---|
| 0 | 2 (A) | 9 (B) | no |
| 1 | 3 (A) | 5 (B) | no |
| 2 | 7 (A) | ∅ → jump to A | no |
| 3 | ∅ → jump to B | 2 (A) | no |
| 4 | 9 (B) | 3 (A) | no |
| 5 | 5 (B) | 7 (A) | no |
| 6 | ∅ | ∅ | yes → both null → return null |
Contrast with Dry run 1: there the pointers coincided on a real node (the join). Here there is no join, so the only place they can coincide is null — and because each walks exactly lenA + lenB nodes, they arrive there together. That is why the switch uses the other head at null instead of skipping null.
Both runs step exactly as the tables above. Python compares identity with a is not b; on reaching the end it switches with a = a.next if a else headB.
Same steps. C++ compares the pointers with a != b; the switch is a = a ? a->next : headB;
Same steps. Java compares the references with a != b; the switch is a = (a != null) ? a.next : headB;