Start learning free

LESSON 279 OF 347 · LINKED LISTS

Intersection of Two Linked Lists in C#: 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 null.

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 null.

 

Constraints:

  • The number of nodes of listA is in the m.
  • The number of nodes of listB is in the n.
  • 1 <= m, n <= 3 * 104
  • 1 <= Node.val <= 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

    C# · LINKED LISTS

    Intersection Of Two Linked Lists in C#, explained

    Problem overview

    You get 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. GetIntersectionNode returns that first shared node, or null if the lists never meet. There are no cycles, and the lists must keep their original structure.

    In the first example, list A is 4, 1, 8, 4, 5 and list B is 5, 6, 1, 8, 4, 5. The two lists share the tail 8, 4, 5, so the answer is the node holding 8. Notice that both lists also contain a node with value 1, but those are two different nodes, so they are not an intersection. An intersection means the same node object, not equal values.

    The follow-up asks for O(m + n) time with O(1) memory, where m and n are the list lengths.

    Approach: align the ends, then walk together

    A simple solution stores every node of A in a HashSet<ListNode> and then walks B until it finds a node in the set. That runs in linear time but uses O(m) memory.

    The constant-memory idea comes from one observation: after the intersection point, both lists are identical, so the part after the intersection has the same length in both. Any difference in total length therefore comes from the nodes before the intersection. If you skip the extra nodes at the front of the longer list, the two pointers are the same distance from the end, and from the intersection point. Walk them forward in lockstep and they land on the shared node at the same moment. If there is no intersection, both reach null at the same moment, and null == null ends the loop with the right answer.

    The steps are:

    1. Count the nodes of each list.
    2. Advance the pointer of the longer list by the difference in length.
    3. Advance both pointers one node at a time until they refer to the same node.

    With A of length 5 and B of length 6, B's pointer skips 1 node and moves to the node holding 6. The pointers then visit the pairs (4, 6), (1, 1) and (8, 8), where the first number comes from A and the second from B. The two 1s are different nodes, so the loop continues. The 8s are the same node, so it stops there and returns it.

    Step by step

    1. Measure both lists with a helper, Size(head), giving sizeA and sizeB. You write the helper afterwards.
    2. Create two walkers, nA and nB, starting at the heads.
    3. Compare the lengths. If sizeA < sizeB, B is longer, so advance nB by sizeB - sizeA using a second helper, MoveNode(node, k). Otherwise advance nA by sizeA - sizeB. When the lengths are equal the difference is 0 and nothing moves. MoveNode can't change which node the caller's variable refers to; it returns the node where it stopped, so assign the result back.
    4. Walk together with while (nA != nB), moving both to their next.
    5. Return nA. It is the shared node, or null when the lists don't meet.
    6. Write Size as a private method: a counter, a loop while head != null that increments it and moves head forward, and then return count.
    7. Write MoveNode as a private method: a loop while k > 0 that moves node forward and decrements k, and then return node.

    The solution in C#

    class Solution {
        public ListNode GetIntersectionNode(ListNode headA, ListNode headB) {
            int sizeA = Size(headA);
            int sizeB = Size(headB);
            ListNode nA = headA;
            ListNode 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;
        }
    
        private int Size(ListNode head) {
            int count = 0;
            while (head != null) {
                count++;
                head = head.next;
            }
            return count;
        }
    
        private ListNode MoveNode(ListNode node, int k) {
            while (k > 0) {
                node = node.next;
                k--;
            }
            return node;
        }
    }
    

    C# details worth knowing

    • != compares references. ListNode is a class that doesn't overload the operator, so nA != nB asks whether the two variables refer to different node objects. It does not compare the values inside. That is exactly what this problem needs.
    • Parameters are copies. In Size, moving head along the list changes only the method's own copy of the reference. The caller's headA stays where it was. This is also why MoveNode has to return its result.
    • private helpers. Members of a class are private unless you say otherwise, and marking helpers private explicitly makes the intent clear. Method names in C# use PascalCase, like GetIntersectionNode, Size and MoveNode.
    • The lists are not modified. The code only reads next, so both lists keep their structure, as the problem requires.

    Complexity

    Counting the lists costs m + n steps. Aligning the pointers costs at most |m - n|. The final walk covers at most the shorter length. All of these are bounded by m + n, so the time is O(m + n). The method keeps a few integers and two references, so the extra space is O(1).

    Edge cases and common mistakes

    • No intersection. After alignment both pointers reach null together, so the loop ends and returns null. For [2, 6, 4] and [1, 5] the answer is null.
    • Equal values, different nodes. Two lists with the same values but no shared nodes also return null. Never compare val fields to decide whether the nodes are the same.
    • Intersection at a head. If the lists are the same list, the lengths match, nothing is skipped, and the first comparison already succeeds.
    • Skipping the wrong list. Advancing the shorter list lets the pointers walk past each other, and the loop may never find a match or may run into null and throw NullReferenceException.
    • Forgetting to assign the result of MoveNode. Calling MoveNode(nB, 1); alone leaves nB unchanged.
    • Changing the lists to find the answer. Reversing or cutting nodes would break the requirement to keep the original structure.

    Testing

    Results from running the solution:

    • The first example, with a shared tail starting at 8, returns the shared node, which compares equal (==) to the node the tails share.
    • [2, 6, 4] and [1, 5] with nothing shared returns null.
    • Two separate lists [1, 2] and [1, 2] return null.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free