Start learning free

LESSON 305 OF 415 · LINKED LISTS

Intersection of Two Linked Lists in Swift: O(m + n) Time, O(1) Space

Practice O(n) Read the full explanation

Given the heads of two singly linked-lists headA and headB, return the node at which the two lists intersect. If the two linked lists have no intersection at all, return nil.

For example, the following two linked lists begin to intersect at node c1:



The test cases are generated such that there are no cycles anywhere in the entire linked structure.

Note that the linked lists must retain their original structure after the function returns.

Custom Judge:

The inputs to the judge are given as follows (your program is not given these inputs):

  • intersectVal - The value of the node where the intersection occurs. This is 0 if there is no intersected node.
  • listA - The first linked list.
  • listB - The second linked list.
  • skipA - The number of nodes to skip ahead in listA (starting from the head) to get to the intersected node.
  • skipB - The number of nodes to skip ahead in listB (starting from the head) to get to the intersected node.

The judge will then create the linked structure based on these inputs and pass the two heads, headA and headB to your program. If you correctly return the intersected node, then your solution will be accepted.

 

Example 1:



Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
Output: Intersected at '8'
Explanation: The intersected node's value is 8 (note that this must not be 0 if the two lists intersect).
From the head of A, it reads as [4,1,8,4,5]. From the head of B, it reads as [5,6,1,8,4,5]. There are 2 nodes before the intersected node in A; There are 3 nodes before the intersected node in B.

Example 2:



Input: intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
Output: Intersected at '2'
Explanation: The intersected node's value is 2 (note that this must not be 0 if the two lists intersect).
From the head of A, it reads as [1,9,1,2,4]. From the head of B, it reads as [3,2,4]. There are 3 nodes before the intersected node in A; There are 1 node before the intersected node in B.

Example 3:



Input: intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
Output: No intersection
Explanation: From the head of A, it reads as [2,6,4]. From the head of B, it reads as [1,5]. Since the two lists do not intersect, intersectVal must be 0, while skipA and skipB can be arbitrary values.
Explanation: The two lists do not intersect, so return nil.

 

Constraints:

  • The number of nodes of listA is m.
  • The number of nodes of listB is n.
  • 1 <= m, n <= 3 * 104
  • The value of each node is between 1 and 105
  • 0 <= skipA < m
  • 0 <= skipB < n
  • intersectVal is 0 if listA and listB do not intersect.
  • intersectVal == listA[skipA] == listB[skipB] if listA and listB intersect.

 

Follow up: Could you write a solution that runs in O(m + n) time and use only O(1) memory?
STEP 1 OF 17
    Ctrl + Enter to check

    SWIFT · LINKED LISTS

    Intersection Of Two Linked Lists in Swift, explained

    Problem overview

    The method getIntersectionNode receives the heads of two singly linked lists, headA and headB. The lists may merge into one shared tail. If they do, the method returns the first node that belongs to both lists. If they never meet, it returns nil. The lists have no cycles, and they must keep their structure: the method reads them and changes nothing.

    In the first example, list A is [4, 1, 8, 4, 5] and list B is [5, 6, 1, 8, 4, 5]. A has two nodes before the shared part and B has three. The shared part starts at the node holding 8, so that node is the answer. In the third example, [2, 6, 4] and [1, 5] never meet, so the answer is nil.

    The intersection is about identity, not values. Two different nodes that both hold 8 don't count. The answer is a node that both lists reach through their next links.

    Approach: cut the longer list down to size

    The simple idea is to take each node of A and search B for it, which is O(m * n). Storing the nodes of A in a set brings the time down to O(m + n) but uses O(m) memory. The statement's follow-up asks for linear time with constant memory.

    The key observation is that once two lists merge, they stay merged: everything after the meeting node is the same chain, with the same length for both lists. So the nodes after the intersection don't differ in count. Only the part before it does, and the longer list has extra nodes there.

    If the longer list skips its extra nodes, both pointers are the same distance from the end. Moving them forward one node at a time, they reach the intersection in the same step. If there is no intersection, they both reach nil in the same step. In both cases the loop "stop when the pointers are the same object" finishes, and the pointer holds the answer.

    In the first example, A has 5 nodes and B has 6, so B skips one node and starts at the node holding 6, while A starts at its head, 4. Stepping together, they visit (4, 6), then (1, 1), then (8, 8), and stop because both are the same node.

    Step by step

    1. Measure both lists. A helper size returns the number of nodes. The lesson uses it first and writes it afterwards: sizeA and sizeB.
    2. Create two walkers nA and nB starting at the heads. They are var, since they move.
    3. Find the longer list. If sizeA < sizeB, B is longer, so advance nB by sizeB - sizeA nodes using the second helper, moveNode. Otherwise advance nA by sizeA - sizeB. When the sizes are equal, the second branch advances by 0, which does nothing.
    4. Assign the result back. moveNode returns the node it stopped on. It can't change which node the caller's variable refers to, so the result must be stored, as in nB = moveNode(nB, ...).
    5. Walk together while nA !== nB, moving both one node with ?.next.
    6. Return nA. It is the intersection node, or nil if the walkers ran off the ends together.
    7. Write size. Copy the parameter into a var node, count while node != nil, and return the count.
    8. Write moveNode. Copy both parameters into mutable locals, then advance n while k > 0, counting k down.

    The solution in Swift

    class Solution {
        func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {
            let sizeA = size(headA)
            let sizeB = size(headB)
            var nA = headA
            var nB = headB
            if sizeA < sizeB {
                nB = moveNode(nB, sizeB - sizeA)
            } else {
                nA = moveNode(nA, sizeA - sizeB)
            }
            while nA !== nB {
                nA = nA?.next
                nB = nB?.next
            }
            return nA
        }
    
        func size(_ head: ListNode?) -> Int {
            var count = 0
            var node = head
            while node != nil {
                count += 1
                node = node?.next
            }
            return count
        }
    
        func moveNode(_ node: ListNode?, _ k: Int) -> ListNode? {
            var n = node
            var k = k
            while k > 0 {
                n = n?.next
                k -= 1
            }
            return n
        }
    }
    

    The Swift details that matter here:

    • !== compares identity. ListNode is a class, so nA !== nB asks whether both variables refer to the very same object. It works on optionals too, and two nil values count as identical, which is how the loop ends when there is no intersection. Writing nA != nB would not compile, because ListNode doesn't conform to Equatable.
    • Parameters are constants. A function can't change its own parameters in Swift. That's why size starts with var node = head, and why moveNode declares var k = k. The new local variable shadows the parameter of the same name.
    • Optional chaining. nA?.next gives the next node when nA has a value, and nil when nA is nil. No ! is needed, so the walk can safely step past the last node.
    • Methods call each other by name. Inside a class, size(headA) means self.size(headA). The helpers can be declared below the method that uses them.

    Complexity

    Let m and n be the lengths of the lists.

    Time: O(m + n). Measuring takes m + n steps. The alignment skips at most the longer length. The final loop makes at most min(m, n) steps, since after alignment the walkers have at most that many nodes left.

    Space: O(1). The code keeps a few integers and two references.

    Edge cases and common mistakes

    • No intersection. The walkers reach nil together after the alignment, and the method returns nil. For [2, 6, 4] and [1, 5] the result is nil.
    • Equal values, different nodes. Two separate lists [1, 2] and [1, 2] hold equal numbers but share no node. The method returns nil, because it compares identity, not val.
    • Intersection at a head. If both heads are the same node, the sizes are equal, nothing is skipped, and the loop condition is false at once. The method returns that head.
    • Lists of different length. The common mistake is to start both walkers at the heads. They then pass each other's positions at different times and never line up on the shared node.
    • Modifying the lists. The statement requires the original structure afterwards. This solution only follows next links and never assigns to them.
    • Comparing with val. Values can repeat before the intersection. Compare nodes, not numbers.

    Testing

    • A = [4, 1] followed by the shared nodes [8, 4, 5], B = [5, 6, 1] followed by the same shared nodes: returns the node holding 8.
    • A = [2, 6, 4], B = [1, 5], no shared nodes: returns nil.
    • The same list passed as both arguments: returns its head.
    • Two separate lists [1, 2] and [1, 2]: returns nil.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free