5 Best Ways to Find Union of Two Given Linked Lists in Python

πŸ’‘ Problem Formulation: In computing, particularly in data structure manipulation, finding the union of two linked lists is a common problem. It involves combining the elements from two linked lists, ensuring there are no duplicate elements in the resultant list. If we take linked list A with elements [1, 2, 3] and linked list B with elements [3, 4, 5], the union would result in [1, 2, 3, 4, 5].

Method 1: Using Iteration and Storage

This method involves iterating over the two linked lists and storing the unique elements in a set. The set ensures that duplicates are removed automatically. Finally, we can convert the set back into a linked list, giving us the union of the original lists.

Here’s an example:

class ListNode:
    def __init__(self, value=0, next=None):
        self.val = value
        self.next = next

def union(list1, list2):
    elements = set()
    current = list1
    while current:
        elements.add(current.val)
        current = current.next
    current = list2
    while current:
        elements.add(current.val)
        current = current.next
    dummy = ListNode()
    current = dummy
    for val in elements:
        current.next = ListNode(val)
        current = current.next
    return dummy.next

# Example linked lists:
# List A: 1 -> 2 -> 3
# List B: 3 -> 4 -> 5

# Merge into a new linked list:
# Union: 1 -> 2 -> 3 -> 4 -> 5

The output of this code would produce a linked list containing the values 1 -> 2 -> 3 -> 4 -> 5.

This code snippet creates a set to collect all unique elements from both linked lists. It traverses each linked list node by node, adding values to the set. Once all the elements are collected, it generates a new linked list from the set elements, thus creating the union of the two linked lists.

Method 2: Using Class Inheritance

This method leverages Python’s class inheritance by extending list node class functionality to include a method for union. We thereby create more encapsulated and reusable code.

Here’s an example:

class ListNode:
    # ... (ListNode definition as before)
    
class LinkedList:
    def __init__(self, head=None):
        self.head = head

    def to_set(self):
        result = set()
        current = self.head
        while current:
            result.add(current.val)
            current = current.next
        return result

    @staticmethod
    def from_set(elements):
        dummy = ListNode()
        current = dummy
        for val in elements:
            current.next = ListNode(val)
            current = current.next
        return LinkedList(dummy.next)

    def union(self, other):
        union_set = self.to_set().union(other.to_set())
        return LinkedList.from_set(union_set)
        
# Usage:
# list1 and list2 as instances of LinkedList
# list1.union(list2)

The output and final linked list structure remain the same, containing 1 -> 2 -> 3 -> 4 -> 5.

In this approach, we define a LinkedList class that wraps around our ListNode, providing utility methods to convert between sets and linked lists. By extending the class functionality, we achieve a more object-oriented solution, separating concerns neatly, and increasing code readability.

Method 3: Using Recursion

Recursion can be a more elegant solution to find the union of two linked lists, especially when seeking to maintain the order without using additional space like sets.

Here’s an example:

class ListNode:
    # ... (ListNode definition as before)
    
def add_to_result(result, node):
    if not node:
        return
    if node.val not in result:
        result.add(node.val)
        result.tail.next = ListNode(node.val)
        result.tail = result.tail.next
    add_to_result(result, node.next)

def union_recursive(list1, list2):
    dummy = ListNode()
    result = dummy
    dummy.tail = dummy
    result_set = set()
    add_to_result(result_set, list1)
    add_to_result(result_set, list2)
    return dummy.next

# Usage:
# union_recursive(list1.head, list2.head)

The output would result in a linked list with the elements 1 -> 2 -> 3 -> 4 -> 5.

This recursive approach starts with a dummy node. It iteratively checks and adds unique elements to a result set and the linked list. If the element does not exist in result set, it is appended to the result linked list. The recursive nature obviates the need for explicit loops. However, recursion can be less performant for large lists due to stack size limitations.

Method 4: Using Generator Expressions

Python’s generator expressions can be utilized for a concise and lazy-evaluation approach to process elements from both linked lists and form the union in an efficient way.

Here’s an example:

class ListNode:
    # ... (ListNode definition as before)

def list_to_generator(list_node):
    while list_node:
        yield list_node.val
        list_node = list_node.next

def union_generators(gen1, gen2):
    elements = set()
    for val in gen1:
        if val not in elements:
            elements.add(val)
            yield val
    for val in gen2:
        if val not in elements:
            elements.add(val)
            yield val

# Usage:
# list1_gen = list_to_generator(list1)
# list2_gen = list_to_generator(list2)
# union_gen = union_generators(list1_gen, list2_gen)
# result = LinkedList.from_iterable(union_gen)

Output will be the sequential generation of values: 1, 2, 3, 4, 5, which can be consumed to create the union linked list.

The use of generator expressions allows for a memory-efficient iteration over the linked list elements. Unique elements are yielded one at a time, and duplicates are ignored, thanks to the set. This method is particularly effective for large linked lists, as it doesn’t require creating the intermediate list in memory.

Bonus One-Liner Method 5: Using Comprehensions and Set Union

A Pythonic one-liner using comprehensions and set union can be seen as a combination of the best parts of the previous methods. It is concise and expressive.

Here’s an example:

def union_oneliner(list1, list2):
    return set(val for node in (list1, list2) for val in list_to_generator(node))

# Usage:
# The one-liner returns a set which can be converted back to a LinkedList.

If you’d run this code with our example lists, it’d result in a set {1, 2, 3, 4, 5}, which you would convert to a linked list.

This one-liner takes advantage of Python’s set operations to perform unions. By using a generator expression that iterates over both lists, it creates a set with all unique elements effectively combining both linked lists into a single set. This set can then be used to form a new linked list.

Summary/Discussion

  • Method 1: Iteration and Storage. Strengths: Simple and intuitive. Weaknesses: Requires conversion between data structures.
  • Method 2: Class Inheritance. Strengths: Object-oriented, reusable. Weaknesses: More complex setup.
  • Method 3: Recursion. Strengths: Elegant, maintains order. Weaknesses: Not efficient for large lists due to recursion limits.
  • Method 4: Generator Expressions. Strengths: Efficient, memory-friendly. Weaknesses: Less straightforward, requires understanding of generators.
  • Method 5: One-Liner Comprehension. Strengths: Pythonic, extremely concise. Weaknesses: Can be less readable for those unfamiliar with comprehensions.