5 Best Ways to Find the First Common Element Between Two Linked Lists in Python

πŸ’‘ Problem Formulation: Given two singly linked lists, the objective is to find the first common element between them. For instance, if the first linked list is 1 -> 2 -> 3 -> 4 and the second is 6 -> 3 -> 4, the desired output is 3, as it is the first element that appears in both lists.

Method 1: Hash Table Approach

This approach entails traversing the first linked list and storing each element in a hash table (a set in Python). Then, we traverse the second linked list and check each element against the hash table. The first match is the first common element. This method offers O(m+n) time complexity, where m and n are the lengths of the linked lists.

Here’s an example:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

def common_element_hash_table(list1, list2):
    seen = set()
    while list1:
        seen.add(list1.value)
        list1 = list1.next
    while list2:
        if list2.value in seen:
            return list2.value
        list2 = list2.next
    return None

# Example usage
head1 = Node(1)
head1.next = Node(2)
head1.next.next = Node(3)
head1.next.next.next = Node(4)

head2 = Node(6)
head2.next = Node(3)
head2.next.next = Node(4)

print(common_element_hash_table(head1, head2))

Output:

3

In this code snippet, we declare a Node class for our linked list, then define a function common_element_hash_table() that takes two linked lists. We iterate over the first list adding each element to a set, then iterate over the second list to find the first common element. Finally, we call the function with two linked lists and print the result.

Method 2: Brute Force

The brute force method involves comparing each node of the first linked list with each node of the second linked list until a common element is found. This approach has a time complexity of O(m*n), making it less efficient for long lists.

Here’s an example:

def common_element_brute_force(list1, list2):
    while list1 is not None:
        current = list2
        while current is not None:
            if list1.value == current.value:
                return list1.value
            current = current.next
        list1 = list1.next
    return None

# Example usage
# (Assuming the same Node class and linked lists from the previous example)
print(common_element_brute_force(head1, head2))

Output:

3

This function, common_element_brute_force(), straightforwardly compares each element of the first list with each element of the second list using two nested loops, returning the first common value found or None if no commonality exists.

Method 3: Two Pointers Approach

The two pointers approach is efficient when both linked lists are sorted. Pointers start at the heads of their respective lists, advancing independently until a common element is found, boasting a time complexity of O(m+n).

Here’s an example:

def common_element_two_pointers(list1, list2):
    while list1 and list2:
        if list1.value == list2.value:
            return list1.value
        elif list1.value < list2.value:
            list1 = list1.next
        else:
            list2 = list2.next
    return None

# Example usage
# (Assuming the same Node class and linked lists from the previous examples)
print(common_element_two_pointers(head1, head2))

Output:

3

The common_element_two_pointers() function uses two pointers, which move through both lists. When a common element is found, or one of the list ends, the search stops. If a common element is found, it is returned; otherwise, None is returned.

Method 4: Recursive Approach

A recursive solution relies on the call stack to compare the nodes of the two linked lists. Note that recursion may lead to a stack overflow on very large lists. This method is also O(m+n).

Here’s an example:

def common_element_recursive(list1, list2):
    if list1 is None or list2 is None:
        return None
    if list1.value == list2.value:
        return list1.value
    return common_element_recursive(list1.next, list2) or common_element_recursive(list1, list2.next)

# Example usage
# (Assuming the same Node class and linked lists from the previous examples)
print(common_element_recursive(head1, head2))

Output:

3

The common_element_recursive() function works by comparing the current nodes of both lists and then moving to the next node of either one or both lists when necessary. The recursion continues until a common element is found or one list runs out of nodes.

Bonus One-Liner Method 5: Using List Comprehension with Hashing

This one-liner is a concise way to combine list comprehension with hashing. It converts one linked list to a set and then iterates over the second linked list, producing a list of common elements.

Here’s an example:

common_element_one_liner = lambda list1, list2: next((node.value for node in list2 if node.value in set(node.value for node in list1)), None)
# Example usage
print(common_element_one_liner(head1, head2))

Output:

3

This one-liner defines a lambda function that takes two lists. It creates a set from the first list and then uses list comprehension to search for the first element in the second list that is also in the set. It returns the first common element found, or None if there is no match.

Summary/Discussion

  • Method 1: Hash Table Approach. Strengths: Efficient with O(m+n) complexity and simpler to understand. Weaknesses: Requires additional memory for the hash table.
  • Method 2: Brute Force. Strengths: Simple to implement and understand. Weaknesses: Least efficient with O(m*n) complexity, not suitable for large lists.
  • Method 3: Two Pointers Approach. Strengths: Efficient with sorted lists. Weaknesses: Inapplicable if lists are not sorted.
  • Method 4: Recursive Approach. Strengths: Elegant recursive solution. Weaknesses: Risk of stack overflow with very large lists.
  • Bonus Method 5: One-Liner with List Comprehension. Strengths: Extremely concise. Weaknesses: Potentially less readable and does not inherently work with data structures other than lists.