Start learning free

LESSON 280 OF 348 · LINKED LISTS

Intersection of Two Linked Lists in Kotlin: 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

    KOTLIN · LINKED LISTS

    Intersection Of Two Linked Lists in Kotlin, explained

    Problem overview

    The function getIntersectionNode receives the heads of two singly linked lists, headA and headB. The lists may merge into one shared tail. It must return the first node that both lists share, or null when they never meet. The lists must keep their original structure, and the input has no cycles.

    Take list A as 4 -> 1 -> 8 -> 4 -> 5 and list B as 5 -> 6 -> 1 -> 8 -> 4 -> 5, where the nodes from 8 onward are the same objects in both lists. The answer is the node holding 8. Both lists contain a node with value 1 before that point, but those are two different nodes, so they don't count. The question is about shared nodes, not equal values.

    The statement asks for O(m + n) time and O(1) extra memory, where m and n are the list lengths.

    Approach: align the lists by length

    A simple solution puts every node of A in a set, then walks B until it finds a node in the set. That is linear in time but uses O(m) memory. Another solution compares every node of A with every node of B, which needs no extra memory but takes O(m * n) time.

    The lesson uses a better observation. Once two lists merge, they stay merged, so the part after the intersection is identical in both and has the same length. If A has 2 nodes before the intersection and B has 3, B is simply one node longer in total. When you skip those extra nodes at the front of the longer list, both pointers have the same number of nodes left. Moving them forward one step at a time then makes them land on the intersection node at the same moment. If there is no intersection, they both fall off the end together, and both become null.

    Step by step

    1. Measure both lists with a helper called size. Their lengths decide how far the longer list must be advanced.
    2. Create two walking pointers, nA and nB, as vars starting at the heads. The parameters themselves can't be reassigned, and the walkers will move.
    3. Find the longer list. If sizeA < sizeB, advance nB by sizeB - sizeA nodes using a second helper, moveNode. Otherwise advance nA by sizeA - sizeB. When the lengths are equal the distance is 0 and nothing moves.
    4. Walk in lockstep while nA !== nB, advancing both pointers by one node each time.
    5. Return nA. The loop ends when the pointers refer to the same node, which is the intersection, or when both are null, which means no intersection.
    6. Write size. Copy the parameter into a var, count nodes until it becomes null, and return the count.
    7. Write moveNode. Copy both parameters into local variables, follow next k times and return where you ended up.

    The solution in Kotlin

    class Solution {
        fun getIntersectionNode(headA: ListNode?, headB: ListNode?): ListNode? {
            val sizeA = size(headA)
            val 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
        }
    
        fun size(head: ListNode?): Int {
            var count = 0
            var node = head
            while (node != null) {
                count++
                node = node?.next
            }
            return count
        }
    
        fun moveNode(node: ListNode?, k: Int): ListNode? {
            var n = node
            var kk = k
            while (kk > 0) {
                n = n?.next
                kk--
            }
            return n
        }
    }
    

    The Kotlin details worth knowing:

    • !== is the key operator. In Kotlin, == calls equals, and !== compares references: it is true when the two variables point at different objects. Two different nodes can hold the same value, so the loop must compare identity. For an ordinary class that doesn't override equals, != would also compare references, but !== says what you mean and survives a later change to the class.
    • Nullable types. A ListNode? may be null, so nA?.next is the safe call: it gives null when nA is null and the next node otherwise. That lets both walkers step past the end without a crash.
    • Parameters are read-only. A Kotlin function parameter can't be reassigned, which is why size and moveNode first copy them into var node, var n and var kk.
    • The extra ?. in size. Inside while (node != null), Kotlin already knows the local variable node is not null, so node.next would be enough. The ?. is harmless, but the compiler may flag it as unnecessary.
    • if/else covers equal lengths. With equal sizes the else branch runs and calls moveNode(nA, 0), which returns nA unchanged.

    Complexity

    Time: O(m + n). size visits every node of each list once. moveNode takes at most max(m, n) steps, and the final loop takes at most min(m, n) steps, since after alignment the shorter list's length bounds it. Each part is linear.

    Space: O(1). Only a few integers and pointers are used, and nothing is stored per node. The lists aren't modified, which satisfies the requirement to keep their structure.

    Edge cases and common mistakes

    • No intersection. For A = 2 -> 6 -> 4 and B = 1 -> 5, B is advanced past nothing, A is advanced by 1 node, and the walkers step together until both are null. The function returns null.
    • Equal values, different nodes. Two separate lists 1 -> 2 and 1 -> 2 share no node, so the answer is null. Comparing val fields would wrongly report an intersection.
    • Intersection at a head. If both heads are the same node, the lengths match, nothing moves, and the loop doesn't run. The function returns that head.
    • One list inside the other. If B is a suffix of A, B's head is the intersection. A is advanced until it reaches that node.
    • Advancing the wrong list. The extra nodes belong to the longer list. Advancing the shorter one would make the pointers walk past each other, and they would never meet.
    • Forgetting to assign the result. moveNode(nB, d) doesn't change nB. It returns the node it stopped on, and that value has to be stored back.

    Testing

    • A = 4, 1, 8, 4, 5 and B = 5, 6, 1, 8, 4, 5 sharing the tail from 8: returns the node with value 8.
    • A = 1, 9, 1, 2, 4 and B = 3, 2, 4 sharing the tail from 2: returns the node with value 2.
    • A = 2, 6, 4 and B = 1, 5, nothing shared: returns null.
    • The same list passed as both arguments: returns its head.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free