5 Best Ways to Restore the Array from Adjacent Pairs in Python

πŸ’‘ Problem Formulation: You are given a list of adjacent pairs where each pair [x, y] shows that x is adjacent to y in an array. The task is to reconstruct the array from these pairs. The array has distinct elements and for pairs [[3, 1], [4, 3], [1, 2]], the expected output is [4, 3, 1, 2].

Method 1: Hash Map and Set

This method involves creating a hash map to store adjacent pairs, and a set to find the start of the array. By locating the element that appears only once in the hash map, one can find the starting element and reconstruct the array.

Here’s an example:

def restoreArray(pairs):
    map_adj = {x: [] for pair in pairs for x in pair}
    for a, b in pairs:
        map_adj[a].append(b)
        map_adj[b].append(a)
    for x in map_adj:
        if len(map_adj[x]) == 1:
            start = x
            break
    array = [start]
    prev = start
    while len(array) < len(map_adj):
        [next] = [x for x in map_adj[prev] if x not in array]
        array.append(next)
        prev = next
    return array

print(restoreArray([[3, 1], [4, 3], [1, 2]]))

The output of this code snippet:

[4, 3, 1, 2]

In this code snippet, the restoreArray function reconstructs the array from a list of adjacent pairs by first creating a map of adjacent elements to find the starting element of the array. Then, by iteratively following the adjacent elements, it constructs the full array.

Method 2: Using Deque

Utilize collections.deque to maintain an order of elements for efficient insertions at both ends. We start by inserting the first unique pair and then keep appending or prepending the connected elements until the array is restored.

Here’s an example:

from collections import deque

def restoreArray(pairs):
    map_adj = {x: [] for pair in pairs for x in pair}
    for a, b in pairs:
        map_adj[a].append(b)
        map_adj[b].append(a)
    array = deque()
    for x in map_adj:
        if len(map_adj[x]) == 1:
            array.append(x)
            break
    while len(array)  1:
            array.appendleft(map_adj[x][1] if map_adj[x][0] == array[1] else map_adj[x][0])
        if len(map_adj[y]) > 1:
            array.append(map_adj[y][1] if map_adj[y][0] == array[-2] else map_adj[y][0])
    return list(array)

print(restoreArray([[3, 1], [4, 3], [1, 2]]))

The output of this code snippet:

[4, 3, 1, 2]

The restoreArray function uses a double-ended queue to efficiently rebuild the array by appending or prepending the relevant elements. By consistently adding elements to the deque until all pairs are connected, the order of the original array is restored.

Method 3: Recursive Backtracking

Recursive Backtracking can be implemented to rebuild the array. Starting from one pair, recursively search for the next adjacent elements that are not yet in the path and backtrack if a dead end is encountered.

Here’s an example:

def restoreArray(pairs):
    map_adj = {x: [] for pair in pairs for x in pair}
    for a, b in pairs:
        map_adj[a].append(b)
        map_adj[b].append(a)

    def backtrack(start, path):
        if len(path) == len(map_adj):
            return path
        for next in map_adj[start]:
            if next not in path:
                res = backtrack(next, path + [next])
                if res: return res

    for x in map_adj:
        if len(map_adj[x]) == 1:
            return backtrack(x, [x])

print(restoreArray([[3, 1], [4, 3], [1, 2]]))

The output of this code snippet:

[4, 3, 1, 2]

The restoreArray function utilizes recursive backtracking to explore all paths until the array is successfully reconstructed. Once the complete path is found, the recursion unwinds, providing the correct sequence of the original array.

Method 4: Graph Theory and DFS

By treating the problem as a graph, where each number represents a node, and each pair is an edge, Depth-First Search (DFS) can be applied to traverse the graph to rebuild the array.

Here’s an example:

def restoreArray(pairs):
    map_adj = {x: [] for pair in pairs for x in pair}
    for a, b in pairs:
        map_adj[a].append(b)
        map_adj[b].append(a)

    def dfs(x):
        array.append(x)
        if len(array) == len(map_adj):
            return array
        for next in map_adj[x]:
            if next not in array:
                return dfs(next)

    array = []
    for x in map_adj:
        if len(map_adj[x]) == 1:
            return dfs(x)

print(restoreArray([[3, 1], [4, 3], [1, 2]]))

The output of this code snippet:

[4, 3, 1, 2]

This function applies a DFS traversal to explore all connected nodes from the starting node until all elements are visited. The dfs function accumulates elements into array to form the restored array as the graph is traversed.

Bonus One-Liner Method 5: Utilizing itertools.chain and Collections Counter

A one-liner solution leveraging itertools.chain to flatten the list of pairs and collections.Counter to count occurrences of elements, finding the start and reconstructing the array through a map.

Here’s an example:

from itertools import chain
from collections import Counter

def restoreArray(pairs):
    return list(chain([start := min(chain(*pairs), key=Counter(chain(*pairs)).get)], (map_adj := {x: [] for x in set(chain(*pairs))}).update({a: map_adj[a] + [b] for a, b in pairs}) or [start] * (len(map_adj) - 1)))

print(restoreArray([[3, 1], [4, 3], [1, 2]]))

The output of this code snippet:

[4, 3, 1, 2]

This one-line function identifies the starting element with minimal frequency in the pair list, builds an adjacency map, and chains the start element with the rest of the elements retrieved from the adjacency map.

Summary/Discussion

  • Method 1: Hash Map and Set. Uses a hash map for element tracking and a set for finding the starting element effectively. However, it requires extra space for the map and linear time to search for the next element.
  • Method 2: Using Deque. This method is efficient for inserting elements at both ends and is memory efficient. It may not be the most intuitive method for array reconstruction.
  • Method 3: Recursive Backtracking. It is a conceptually straightforward solution for finding the correct order, but it may incur a large call stack size for big data sets and can be inefficient in worst-case scenarios.
  • Method 4: Graph Theory and DFS. Applies a classic graph traversal technique which is efficient and guarantees array reconstruction. However, it can be cumbersome to code for beginners.
  • Bonus Method 5: One-Liner. It’s a compact and clever use of Python’s advanced features, but it can be hard to read and understand, thus making debugging difficult.