DSA sheet · Linked List · Basic operations pattern
Intersection of Two Linked Lists
Two linked lists start separately and, at some node, join and share the same tail. We must find that joining node. The teacher uses a running race picture to think of two fair ways to make two pointers arrive at the joining node at the same moment: first by giving the longer list a head start (length difference), then by letting each runner also run the other's track (switching heads). The second one is the classic 4-line answer, and the race idea behind it shows up again in many pointer problems.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Linked list basics you need for this page
- Part A · Length-difference approach (the fair head start)
- Part B · Switch-heads approach (run both tracks)
- Part C · Revision page
Part 0 · Before starting
What is a linked list node?
A linked list is a chain of small boxes called nodes. Each node holds two things: a value (val) and a link to the next node (next). The last node's next is None, which means "the chain ends here".
class ListNode:
def __init__(self, val=0, next=None):
self.val = val # the number stored in this node
self.next = next # the next node, or None at the end[4] → [1] → [8] → [4] → [5] → None ↑ head
- head is a variable that points to the first node. It is the only way into the list. Lose it and the whole list is gone.
- Walking the list means starting a helper pointer at the head and repeating
curr = curr.nextuntilcurrbecomesNone. We use a helper (an "alias") so the realheadstays safe. - No index access. In an array you can jump to position i straight away. A linked list has no positions stored anywhere. To reach the i-th node you must walk i steps from the head, so reaching position i costs O(i), and O(n) in the worst case.
- Save next before changing a pointer. If you overwrite
node.next, the rest of the chain is lost unless you saved it first. (This page never rewires a pointer, it only walks. But keep this rule in mind for every other list problem.)
A node is not its value: identity vs value
This is the key idea for this page. Two different nodes can store the same value. A node is a separate object living at its own place in memory (its address). So "the same node" means the same object, not "a node with the same number".
In Python, a is b checks "are these the very same object?". a.val == b.val only checks "do they hold the same number?". For intersection we need is.
What does "two lists intersect" look like?
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘
From the node 8 onwards, both lists use the very same nodes. Node 8 is stored once in memory. Node 1 of list A and node 2 of list B both have .next pointing to it. So once two lists meet, they can never split again: a node has only one next. The shape is a Y, never an X.
Part A · Length-difference approach
LeetCode 160 · Intersection of Two Linked Lists
1The question in simple words
You get the heads of two singly linked lists, headA and headB. If the lists join at some node, return that node (the first shared one). If they never join, return None.
- We return the node, not its value. Returning the node 8 automatically hands back 8 → 4 → 5, because the nodes are linked.
- LeetCode's example 1: A = 4 → 1 → 8 → 4 → 5, B = 5 → 6 → 1 → 8 → 4 → 5, and the answer is the node with value 8.
- We must not change the lists. They should look the same after our function runs.
2What the constraints tell us
- m (length of A) and n (length of B) can each be up to 3 × 10⁴.
- The obvious brute force is to take every node of A and compare it with every node of B. That is m × n comparisons = 3×10⁴ × 3×10⁴ = 9 × 10⁸. Above roughly 10⁸ operations we expect TLE (Time Limit Exceeded). So the teacher rules out O(n²) at once: we need a linear solution, something like O(m + n).
- Both lists have at least 1 node, so the heads are never
Noneat the start. (Our code still works if one is empty.)
3Intuition: a fair race
Put one runner on each list: pointer a starts at headA, pointer b starts at headB. Both move one node per step. We want them to stand on the same node at the same moment, because the first time that happens is the joining point.
The problem: the lists have different lengths before they join. In the picture above, A has 2 nodes before the join and B has 4. If both start together, a reaches node 8 after 2 steps, but b needs 4. They pass through node 8 at different times and never meet there.
The teacher's picture: in a real race, every runner must get a track of the same length, otherwise the result means nothing. So the longer list's runner gets moved forward first. If B is longer by 2, move b 2 steps ahead, then start the race. Now both runners have the same distance left to the finish (None), and so the same distance left to the joining node.
4Building the logic from examples
First idea: store the values and look for a match. Why it fails
A natural first thought: walk list A, save all its values, then walk list B and stop at the first value that was already seen. Look at the example: list B's first node is 5, and list A contains a 5 at its end. List A also has a 4 at the front and another 4 after the 8. A value match would report the wrong node.
→ Because a node is not just a number. It is an object at its own address in memory, and it carries its own
next link. Two boxes with "4" written on them are still two boxes. The values can repeat; the nodes cannot. So we can't turn the lists into arrays of numbers and compare. We must work with the nodes themselves and compare them with is.Second idea: when do we know we found the answer?
When a and b point to the same node at the same time. That can only happen at the joining node or after it, and the first time it happens is exactly the joining node. So the whole job is to make them arrive at the same time.
Count both lengths, then give the longer one a head start
Take the teacher's board example: A has length 5 (2 own nodes + 3 shared), B has length 7 (4 own nodes + 3 shared).
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘
length A = 5, length B = 7, difference = 2
The shared tail is the same for both, so the whole difference (2) comes from the parts before the join. Move b forward 2 nodes. Now b sits on B's 3rd node (value 1) and a sits on A's 1st node (value 4). Each has exactly 2 nodes left before node 8. Walk both together: after 1 step they are on (1, 2) → different nodes. After 2 steps both are on node 8 → same node, return it.
→ The teacher shows two ways. (1) Compute
diff = lenB - lenA and loop that many times. (2) Simpler: while lenB > lenA: move b one step and do lenB -= 1. The loop stops by itself when the two lengths become equal. With 7 and 5: jump (6 left), jump (5 left), now 5 > 5 is false → stop. Two jumps, just as needed. Write the same loop for the case where A is longer; only one of the two loops will actually run.a and b are both at None. Then what?→ Counting walks each pointer to the end. So before the race we must reset them:
a = headA, b = headB. This is why we counted with aliases and never moved headA/headB themselves.→ After the head start, both have the same number of nodes left. So they reach the end together, and both become
None at the same step. None is None is True, the loop while a is not b stops, and we return a, which is None. That is exactly the "no intersection" answer, so no extra check is needed. You can return a or b, they are the same thing.5Approach steps
- Walk A with an alias and count its length
lenA. Do the same for B to getlenB. - Reset the aliases:
a = headA,b = headB. - While A is longer, move
aone step and reducelenA. While B is longer, movebone step and reducelenB. - Now both have the same number of nodes left. While
aandbare different nodes, move both one step. - Return
a: the joining node, orNoneif they only met at the end.
6Code (Python)
class Solution:
def getIntersectionNode(self, headA, headB):
lenA, lenB = 0, 0
a, b = headA, headB
while a: # count length of A
lenA += 1
a = a.next
while b: # count length of B
lenB += 1
b = b.next
a, b = headA, headB # back to the start line
while lenA > lenB: # A is longer: give it the head start
a = a.next
lenA -= 1
while lenB > lenA: # B is longer: give it the head start
b = b.next
lenB -= 1
while a is not b: # same distance left: run together
a = a.next
b = b.next
return a # joining node, or None7Code line by line
| line | what it means |
|---|---|
| a, b = headA, headB | Make aliases. We move these, never the real heads. |
| while a: lenA += 1 a = a.next | Walk A to the end, counting nodes. When it stops, a is None. |
| while b: ... | Same for B. |
| a, b = headA, headB | Reset both runners to the start, because counting moved them to the end. |
| while lenA > lenB: a = a.next lenA -= 1 | If A is longer, push a forward until the remaining lengths match. Each step removes one node from A's "remaining" count. |
| while lenB > lenA: ... | Same if B is longer. At most one of these two loops runs. |
| while a is not b: | Compare nodes (same object?), not values. Keep going while they are different nodes. |
| a = a.next b = b.next | Both move one step: same speed, same remaining distance. |
| return a | Either the shared node, or None when both fell off the end together. |
8Dry run
Nodes: A's own nodes are a1(4), a2(1). B's own nodes are b1(5), b2(6), b3(1), b4(2). Shared nodes are c1(8), c2(4), c3(5).
| step | pointers | what happens | answer so far |
|---|---|---|---|
| 1 | counting | lenA = 5, lenB = 7 | — |
| 2 | a = a1(4), b = b1(5) | reset to heads | — |
| 3 | b = b2(6) | 7 > 5 → jump, lenB = 6 | — |
| 4 | b = b3(1) | 6 > 5 → jump, lenB = 5. Now 5 > 5 is false → stop | — |
| 5 | a = a1(4), b = b3(1) | different nodes → move both | — |
| 6 | a = a2(1), b = b4(2) | different → move both | — |
| 7 | a = c1(8), b = c1(8) | same node → loop stops | return c1 (8) |
Snapshot after the head start (step 4):
a
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘
b
Snapshot at step 7, both on the same node:
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘ ↑
a, b
Notice the 1 in A and the 1 in B: same value, different nodes. If the runners had ever stood on those two 1s together, a value check would have answered wrongly. Comparing nodes avoids that.
9Complexity & remember
- Time: m steps to count A, n steps to count B, then the head start plus the joint walk, which is at most the longer length again. The teacher adds it up as m + n + max(m, n). If m is the larger, that is about O(2m + n); if both are about n, it is about 3n. Still linear, so it passes.
- Space O(1): just two pointers and two counters.
a is b → return a. Compare nodes, never values.Part B · Switch-heads approach
1The question (same as Part A)
Same input, same output: return the first shared node, or None. The only goal now is to do it in fewer passes: the teacher asks whether we can get from about 3n down to about 2n, with no counting at all.
2What the constraints tell us
- Still up to 3 × 10⁴ nodes each, so O(m + n) is what we want. Part A was already linear; this part is an improvement in the constant (3n → 2n) and in code length.
- We still must not modify the lists, and we still want O(1) extra space.
3Intuition: make both runners run the same total track
In Part A we made the race fair by cutting the longer track (head start). Here is the other way to make it fair, without knowing any lengths: let each runner run both tracks.
- Runner
afirst runs track A. When it reaches the end, it jumps to the start of track B and keeps running. - Runner
bfirst runs track B. When it reaches the end, it jumps to the start of track A and keeps running.
Now both run track A + track B in total, just in a different order. The teacher's numbers: lengths 4 and 6 → one runs 4 + 6 = 10, the other runs 6 + 4 = 10. Same total. Same total means they end at the same moment, and as we'll see, they also reach the joining node at the same moment.
4Why switching heads makes both walk the same distance
Split each list into its parts:
←── x ──→ (A's own part: x nodes)
headA → ● → ● ─┐
├→ ● → ● → ● → None
headB → ● → ● ─┘ ←─── z ───→ (shared tail: z nodes)
←─ y ─→ (B's own part: y nodes)
length of A: m = x + z length of B: n = y + z
Follow each runner until it first reaches the joining node:
| runner | first pass | switch | second pass, until the join | total steps to reach the join the 2nd time |
|---|---|---|---|---|
a | A's own part (x) + shared tail (z), then falls to None | 1 step: None → headB | B's own part (y) | x + z + 1 + y |
b | B's own part (y) + shared tail (z), then falls to None | 1 step: None → headA | A's own part (x) | y + z + 1 + x |
The two totals are the same numbers added in a different order, so they are equal. That is the whole proof. After exactly x + y + z + 1 steps, both runners stand on the joining node together.
- Could they meet earlier, at a wrong node? No. Before that moment, at least one of them is still on a node that belongs only to one list, or they are both in the shared part but at different distances. The very first time they are on the same node is the join. (If x = y, they even meet during the first pass, before anyone switches. That's fine too.)
- What is the
+1? In the code, falling off the end costs one loop step: the runner first becomesNone, and only on the next step does it jump to the other head. Both runners pay this one step, so it doesn't break the equality.
And when the lists never join?
Then z = 0, so there is no shared node to meet at. Runner a walks m nodes, becomes None, switches, walks n nodes, becomes None again. Runner b walks n, None, switches, walks m, None. Both become None at the same step (m + n + 1). Now a is b (both are None), the loop stops, and we return None. Correct answer, no special case.
→ No. Each runner switches only once (after switching, it walks the other list, and at the end of that it is
None at the same time as the other runner). So the loop always ends within about m + n + 2 steps, either at the joining node or at None.None first, instead of jumping from the last node straight to the other head?→ Because of the no-intersection case. If
a never rests on None, the two runners never stand on "the same thing" when the lists are separate, and the loop would never end. Letting both pass through None gives them a common place to meet when there is no join.→ That's why we move aliases
a and b, and keep headA and headB untouched. When a becomes None, we set a = headB. When b becomes None, we set b = headA.The teacher's tip: don't assume a clever trick like this is hard to think of. Relate the problem to a real-life picture (a fair race) and the different approaches appear naturally: either shorten the longer track, or make both run the same combined track.
5Approach steps
- Set
a = headA,b = headB. - While
aandbare different nodes: - — if
aisNone, restart it atheadB; otherwise movea = a.next. - — if
bisNone, restart it atheadA; otherwise moveb = b.next. - When the loop stops,
aandbare the same: the joining node, or bothNone. Returna.
6Code (Python)
class Solution:
def getIntersectionNode(self, headA, headB):
a, b = headA, headB
while a is not b:
if a is None:
a = headB # finished track A, now run track B
else:
a = a.next
if b is None:
b = headA # finished track B, now run track A
else:
b = b.next
return aThe teacher then shortens the two if/else blocks with the ternary operator ("condition ? this : that" in Java). Python's version is x if condition else y:
class Solution:
def getIntersectionNode(self, headA, headB):
a, b = headA, headB
while a is not b:
a = headB if a is None else a.next
b = headA if b is None else b.next
return a7Code line by line
| line | what it means |
|---|---|
| a, b = headA, headB | Two runners at their own start lines. |
| while a is not b: | Keep running until both stand on the same node (or both on None). |
| a = headB if a is None else a.next | If a ran off the end of its current track, move it to the start of B. Otherwise take one normal step. |
| b = headA if b is None else b.next | Mirror image for b: after B it continues on A. |
| return a | The meeting point: joining node or None. b would give the same. |
8Dry run
Same lists as Part A: x = 2 (a1, a2), y = 4 (b1..b4), z = 3 (c1, c2, c3). The formula says they meet after x + z + 1 + y = 2 + 3 + 1 + 4 = 10 steps.
| step | a | b | what happens | same node? |
|---|---|---|---|---|
| 0 | a1(4) | b1(5) | start | no |
| 1 | a2(1) | b2(6) | both step | no |
| 2 | c1(8) | b3(1) | a enters the shared part first (too early) | no |
| 3 | c2(4) | b4(2) | both step | no |
| 4 | c3(5) | c1(8) | b now enters the shared part | no |
| 5 | None | c2(4) | a fell off track A | no |
| 6 | b1(5) | c3(5) | a switches to headB. Both show value 5, but they are different nodes | no |
| 7 | b2(6) | None | b fell off track B | no |
| 8 | b3(1) | a1(4) | b switches to headA | no |
| 9 | b4(2) | a2(1) | both step | no |
| 10 | c1(8) | c1(8) | both arrive together | yes → return 8 |
Snapshot at step 6 (after a switched):
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘ ↑
↑ b
a (a has run 5 nodes + None, now on B)
Snapshot at step 10:
headA → [4] → [1] ─┐
├→ [8] → [4] → [5] → None
headB → [5] → [6] → [1] → [2] ─┘ ↑
a, b (both walked 10 steps)
Step 6 is a nice reminder: value 5 under both pointers, yet not the answer. Only is (same node) is correct.
A no-intersection dry run
A = 1 → 2 → 3, B = 7 → 8 (no shared node). m = 3, n = 2.
| step | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
a | 1 | 2 | 3 | None | 7 | 8 | None |
b | 7 | 8 | None | 1 | 2 | 3 | None |
At step 6 = m + n + 1, both are None → loop ends → return None.
9Complexity & remember
- Time O(m + n): each runner walks at most its own list and then the other one. That is about 2n when the lengths are similar, compared with about 3n in Part A (worst case m + n + max(m, n), or 2m + n when m is the larger).
- Space O(1): two pointers, nothing else.
None together. Code: a = headB if a is None else a.next, same for b with headA.Part C · Revision page
| Store values (rejected) | Length difference | Switch heads | |
|---|---|---|---|
| idea | first value of B also seen in A | cut the longer track: head start by the difference | both run track A + track B |
| correct? | no: values repeat, nodes don't | yes | yes |
| passes over the lists | — | count A, count B, then walk (≈ 3n) | one combined walk (≈ 2n) |
| time | — | O(m + n), worst m + n + max(m, n) | O(m + n) |
| space | O(m) | O(1) | O(1) |
| no intersection | — | both hit None together | both hit None together after m + n + 1 steps |
is, never with values.2. n, m up to 3×10⁴ → n² is 9×10⁸ → TLE → we need linear.
3. Fair race #1: count lengths, move the longer list forward by the difference, then walk together.
4. Fair race #2: when a runner hits
None, restart it at the other head.5. Both cover x + y + z (+1) steps, so they meet at the join, or both become
None.a.val == b.val instead of a is b✗ forgetting to reset the aliases after counting the lengths (Part A)
✗ moving
headA/headB themselves: you then can't switch to them (Part B)✗ jumping from the last node straight to the other head, skipping
None: infinite loop when there is no intersection✗ switching
a to headA instead of headB (it must go to the other list)class ListNode:
def __init__(self, val=0, next=None):
self.val, self.next = val, next
def build(vals):
head = None
for v in reversed(vals):
head = ListNode(v, head)
return head
def tail_of(head):
while head.next:
head = head.next
return head
shared = build([8, 4, 5])
A = build([4, 1]); tail_of(A).next = shared
B = build([5, 6, 1, 2]); tail_of(B).next = shared
s = Solution()
print(s.getIntersectionNode(A, B) is shared) # True
print(s.getIntersectionNode(build([1, 2, 3]), build([7, 8]))) # NoneBased on this video: Intersection of Two Linked Lists