Problem overview
The function getIntersectionNode receives the heads of two singly linked lists, headA and headB. The lists may merge into a shared tail: from some node on, both lists continue through the very same nodes. The function must return that first shared node, or nil if the lists never meet. The lists have no cycles, and they must be left unchanged.
In the first example, list A is 4, 1, 8, 4, 5 and list B is 5, 6, 1, 8, 4, 5. They share the tail 8, 4, 5, so the answer is the node holding 8. It is the same node, not just a node with an equal value: A reaches it after 2 nodes and B after 3. The second 1 in B is a different node from the 1 in A, even though the values match.
The follow-up asks for O(m + n) time and O(1) memory, where m and n are the lengths.
Approach: align the starting points
Two easy solutions exist. Comparing every node of A with every node of B takes O(m * n). Storing the nodes of A in a set and then checking each node of B takes O(m + n) time but O(m) memory. This lesson reaches O(1) memory with a different idea.
If the lists intersect, everything after the meeting point is shared, so that part has the same length in both lists. The two lists can only differ in length before the meeting point. If the longer list's pointer first skips its extra nodes, both pointers are then the same distance from the end. Moving them forward together, one step at a time, makes them arrive at the intersection at the same moment. If the lists never meet, both pointers reach nil together, and nil == nil ends the loop.
On the first example, sizeA is 5 and sizeB is 6, so B's pointer skips 1 node and lands on B's second node (the value 6). Both pointers then have 4 nodes to go. They step together: 4 and 6, then 1 and 1 (two different nodes), then 8 and 8, the same node. The loop stops and the function returns it.
Step by step
- Compute both lengths with a helper:
sizeA := size(headA)andsizeB := size(headB). The helper is written afterwards. - Create walking pointers
nA := headAandnB := headB, so the parameters stay untouched. - Find the longer list. If
sizeA < sizeB, list B is longer. - Skip B's extra nodes:
nB = moveNode(nB, sizeB-sizeA). The helper returns the node it stopped on, so the result must be assigned back. The function receives a copy of the pointer, which it can't change in the caller. - Otherwise A is at least as long, and
nA = moveNode(nA, sizeA-sizeB)skips A's extra nodes. When the lengths are equal, the distance is 0 and nothing moves. - Loop while
nA != nB. Go has nowhile, so this is aforwith only a condition. Comparing pointers with!=asks whether they refer to different nodes, not whether the values differ. - Advance both with
nA = nA.NextandnB = nB.Next. - Return
nA. It is the intersection node, ornilwhen the lists don't meet. - Write
size: a counter starts at 0, and afor head != nilloop increments it and advanceshead. The parameter is a copy, so walking it doesn't disturb the caller. - Write
moveNode: whilek > 0, stepnode = node.Nextand decrementk, then returnnode.
The solution in Go
func getIntersectionNode(headA, headB *ListNode) *ListNode {
sizeA := size(headA)
sizeB := size(headB)
nA := headA
nB := headB
if sizeA < sizeB {
nB = moveNode(nB, sizeB-sizeA)
} else {
nA = moveNode(nA, sizeA-sizeB)
}
for nA != nB {
nA = nA.Next
nB = nB.Next
}
return nA
}
func size(head *ListNode) int {
count := 0
for head != nil {
count++
head = head.Next
}
return count
}
func moveNode(node *ListNode, k int) *ListNode {
for k > 0 {
node = node.Next
k--
}
return node
}
Go details worth knowing:
- Pointer comparison is identity. For two
*ListNodevalues,==is true only if both hold the same address. Two separate nodes with equalValare not equal as pointers. That is exactly the question this problem asks. - Parameters are copies. In
size, the linehead = head.Nextchanges only the local copy of the pointer. InmoveNode, the same is true ofnodeandk, which is why the caller must use the return value. - Helpers can follow their callers. Go resolves package-level functions regardless of the order of declaration, so
sizeandmoveNodemay be written below the function that calls them.
Complexity
Time: O(m + n). size walks each list once, moveNode walks at most the longer list once, and the final loop takes at most as many steps as the shorter list has nodes.
Space: O(1). Four pointers and a few integers, regardless of the list sizes. Nothing is allocated, and the lists are not modified.
Edge cases and common mistakes
- No intersection. For
[2, 6, 4]and[1, 5]the pointers walk together until both arenil, the loop condition becomes false, and the function returnsnil. The loop never dereferences anilpointer, because both pointers becomenilon the same step. - Equal values, different nodes. Two separately built lists
[1, 2]and[1, 2]don't intersect, and the function returnsnil. Comparing.Valinstead of the pointers would wrongly report an intersection. - Intersection at the head. If both heads are the same node, the lengths are equal, nothing is skipped, and the loop doesn't run: it returns
headAimmediately. - Skipping the wrong amount. If the pointers start at different distances from the end, they never meet at the shared node. One of them reaches
nilfirst, and the nextnA.NextornB.Nextpanics with a nil pointer dereference. - Modifying the lists. The statement requires the original structure to remain. This solution only reads
Next.
Testing
- A =
4, 1, 8, 4, 5and B =5, 6, 1, 8, 4, 5, sharing the tail that starts at 8: returns the shared node with value 8. - A =
[2, 6, 4]and B =[1, 5], no shared nodes: returnsnil. - The same list passed as both arguments: returns that list's head.
- Two separately built lists with equal values: returns
nil.