5 Best Ways to Check if Elements of Linked List are Present in Pairs in Python

Rate this post

πŸ’‘ Problem Formulation: When working with a linked list in Python, you may encounter situations where you need to determine if the elements appear in pairs. This means checking if for every element ‘x’ in the linked list, there is another element with the same value ‘x’. For example, given a linked list [1, 2, 3, 2, 1, 3], the output should be True as all elements are paired. However, if the input is [1, 1, 2], the output should be False because the element ‘2’ has no pair.

Method 1: Hash Table

This method involves creating a hash table or dictionary to track the count of each element in the linked list. The hash table will store elements as keys, and their occurrence counts as values. We will iterate through the linked list, incrementing the count for each element. Finally, we will check if all the counts are even, which would confirm that elements are in pairs.

Here’s an example:

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

def is_paired(head):
    counts = {}
    current = head
    while current:
        counts[current.data] = counts.get(current.data, 0) + 1
        current = current.next
    return all(count % 2 == 0 for count in counts.values())

# Example usage:
# Creating a linked list 1->2->3->2->1->3
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)
head.next.next.next = Node(2)
head.next.next.next.next = Node(1)
head.next.next.next.next.next = Node(3)

print(is_paired(head))

Output:

True

This snippet defines a Node class for the linked list and a function is_paired that uses a dictionary to count occurrences. It initializes an example linked list, checks for pairs using the function, and prints the result. This method provides a clear and efficient solution to the problem.

Method 2: Set for Unpaired Elements

The set-based approach maintains a set containing elements currently without a pair. As we iterate through the linked list, we add elements to the set if they’re not already present, or remove them if they are. A paired list will result in an empty set at the end of iteration. This method is efficient when the list is large and the possibility of duplicate pairs is low.

Here’s an example:

def is_paired(head):
    unpaired = set()
    current = head
    while current:
        if current.data in unpaired:
            unpaired.remove(current.data)
        else:
            unpaired.add(current.data)
        current = current.next
    return not unpaired

# Using the previously created linked list...
print(is_paired(head))

Output:

True

This code demonstrates checking for pairs using the set-based method. We iterate through the list, toggling the presence of elements in a set. The absence of elements in the set after iteration signifies all elements are paired. It’s a simple and readable solution with good performance characteristics.

Method 3: Recursion with Pair Removal

A recursive solution can iteratively remove paired elements from the list. If the recursive function encounters a pair, it skips those elements and invokes itself on the remaining list. If the list is empty or contains only one unmatched element, it finishes, and the result indicates if the list is fully paired or not.

Here’s an example:

def remove_pairs(head):
    if head is None or head.next is None:
        return head
    if head.data == head.next.data:
        return remove_pairs(head.next.next)
    head.next = remove_pairs(head.next)
    return head

head = remove_pairs(head)
print(head is None)

Output:

True

This code recursively removes adjacent pairs from the list. If the head’s data equals the next element’s data, those nodes are skipped. The process repeats until we’re left with a list that has no pairs or a single unpaired element. We check if the remaining list is None to determine if the list had elements in pairs.

Method 4: Sorting and Pair Checking

This method involves sorting the elements of the linked list, which will place equal elements next to each other, then checking for pairs becomes a matter of iterating through the sorted list and ensuring that every two consecutive elements are equal. This method may alter the original list order.

Here’s an example:

def is_paired(head):
    # Convert linked list to array for sorting
    array = []
    current = head
    while current:
        array.append(current.data)
        current = current.next
    array.sort()

    # Check for pairs in the sorted array
    for i in range(0, len(array), 2):
        if i+1 >= len(array) or array[i] != array[i+1]:
            return False
    return True

# Using the previously created linked list...
print(is_paired(head))

Output:

True

This method converts the linked list to an array, sorts the array, and then checks if the elements are present in pairs by ensuring that consecutive entries are equal. This is an intuitive way to solve the pairing problem, but it does lose the original ordering of the list.

Bonus One-Liner Method 5: Lambda and Map with Collections

This one-liner solution leverages Python’s collections module to count element occurrences within the linked list with a lambda expression and the map function. It checks if all the elements have an even count, suggesting that each element exists in a pair.

Here’s an example:

from collections import Counter

is_paired = lambda head: all(val % 2 == 0 for val in Counter(map(lambda node: node.data, iter(head))).values())

# Assuming we have a generator to iterate over the linked list...
print(is_paired(head))

Output:

True

The lambda function within this one-liner goes through the linked list, uses map to extract the data values, counts the occurrences using Counter, and checks if all occurrences are even. The clever use of lambda and map makes this a succinct and elegant solution.

Summary/Discussion

  • Method 1: Hash Table. Strengths: Direct and fast with large datasets. Weaknesses: Ram-intensive with very large lists.
  • Method 2: Set for Unpaired Elements. Strengths: Short and effective for lists with a low probability of duplicate pairs. Weaknesses: May be less efficient with many duplicate pairs.
  • Method 3: Recursion with Pair Removal. Strengths: Elegant and works without extra space. Weaknesses: Recursive calls may lead to a stack overflow with very long lists.
  • Method 4: Sorting and Pair Checking. Strengths: Intuitive, especially for those with experience in sorting algorithms. Weaknesses: Alters the original list order, and sorting can be inefficient for larger lists.
  • Method 5: Lambda and Map with Collections. Strengths: Concise, makes use of Python’s powerful functional programming features. Weaknesses: Can be hard to read and understand for beginners.