Facebook Pixel

444. Sequence Reconstruction πŸ”’

Problem Description

You are given an integer array nums of length n that is a permutation of integers from 1 to n. You also have a 2D array sequences where each sequences[i] is a subsequence of nums.

Your task is to determine if nums is both:

  1. The shortest possible supersequence that contains all given subsequences
  2. The only unique shortest supersequence

A supersequence is a sequence that contains all the given subsequences as its subsequences. A subsequence is derived from a sequence by deleting some or no elements without changing the order of remaining elements.

For example:

  • Given sequences = [[1,2],[1,3]], there are two possible shortest supersequences: [1,2,3] and [1,3,2]. Since there are multiple valid shortest supersequences, the answer would be false.
  • Given sequences = [[1,2],[1,3],[1,2,3]], the only shortest supersequence is [1,2,3]. While [1,2,3,4] is also a valid supersequence, it's not the shortest. Since there's only one shortest supersequence, the answer would be true.

The solution uses topological sorting to solve this problem. For each consecutive pair of elements (a, b) in every subsequence, we create a directed edge a β†’ b in a graph. We then perform topological sorting using a queue-based approach:

  1. Build a directed graph from the subsequences and calculate in-degrees for each node
  2. Add all nodes with in-degree 0 to a queue
  3. Process nodes one at a time when the queue has exactly one element (ensuring unique ordering)
  4. For each processed node, reduce the in-degree of its neighbors and add any that reach 0 to the queue
  5. If at any point the queue has more than one element, multiple orderings are possible
  6. Return true if the queue remains empty after processing (unique shortest supersequence exists), false otherwise
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that this problem is about finding a unique topological ordering. Each subsequence gives us ordering constraints - if element a appears before element b in a subsequence, then a must appear before b in any valid supersequence.

Think of it this way: each subsequence tells us "these elements must appear in this relative order." If we combine all these constraints, we're essentially building a directed graph where an edge a β†’ b means "a must come before b."

The critical observation is that for nums to be the only shortest supersequence, there must be exactly one valid way to order all elements while respecting these constraints. This happens when at each step of building the sequence, we have exactly one choice of which element to place next.

In topological sorting terms, this means:

  • When we start, there should be exactly one element with no prerequisites (in-degree 0)
  • After placing that element, there should be exactly one new element that becomes available
  • This pattern continues until all elements are placed

If at any point we have multiple elements with in-degree 0 simultaneously, it means we could choose any of them next, leading to multiple valid orderings. For example, if both elements 2 and 3 have no prerequisites after placing element 1, we could create either [1,2,3,...] or [1,3,2,...] as valid supersequences.

The shortest supersequence will have length n (using each number exactly once). By performing topological sorting and checking that we always have exactly one choice at each step, we can verify both that nums is the shortest possible supersequence and that it's the only one.

Solution Approach

The implementation uses topological sorting with a queue to verify if there's a unique ordering:

1. Graph Construction:

  • Create an adjacency list g with n empty lists (one for each number 1 to n)
  • Create an indeg array to track the in-degree of each node
  • For each subsequence, iterate through consecutive pairs using pairwise(seq)
  • For each pair (a, b), add a directed edge from a-1 to b-1 (converting to 0-indexed)
  • Increment the in-degree of b-1

2. Initialize Queue:

  • Use a deque to store all nodes with in-degree 0
  • These are the nodes that can be placed first (no prerequisites)

3. Process Nodes:

  • While the queue has exactly one element:
    • Remove the single element i from the queue
    • For each neighbor j of node i:
      • Decrement indeg[j] by 1 (removing the edge i β†’ j)
      • If indeg[j] becomes 0, add j to the queue
  • The loop continues only when there's exactly one choice at each step

4. Verify Result:

  • After the loop ends, check if the queue is empty
  • If len(q) == 0: All nodes were processed with a unique ordering β†’ return true
  • If len(q) > 0: Multiple nodes have in-degree 0 simultaneously, meaning multiple valid orderings exist β†’ return false

The algorithm works because:

  • If the queue ever has more than 1 element, the loop breaks, indicating multiple possible orderings
  • If the queue becomes empty before all nodes are processed, there's a cycle (impossible since we have a valid permutation)
  • Only when exactly n nodes are processed one-by-one with no ambiguity do we have a unique shortest supersequence

Time Complexity: O(m) where m is the total number of elements across all subsequences Space Complexity: O(n) for the graph and in-degree arrays

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [1,2,3] and sequences = [[1,2], [1,3], [2,3]].

Step 1: Build the Graph

  • From subsequence [1,2]: Add edge 1β†’2
  • From subsequence [1,3]: Add edge 1β†’3
  • From subsequence [2,3]: Add edge 2β†’3

Converting to 0-indexed:

  • Node 0 (represents 1): edges to [1, 2]
  • Node 1 (represents 2): edges to [2]
  • Node 2 (represents 3): no outgoing edges

In-degrees after building:

  • Node 0: in-degree = 0 (no incoming edges)
  • Node 1: in-degree = 1 (edge from node 0)
  • Node 2: in-degree = 2 (edges from nodes 0 and 1)

Step 2: Initialize Queue

  • Only node 0 has in-degree 0
  • Queue = [0]

Step 3: Process Nodes

Iteration 1:

  • Queue has exactly 1 element (node 0), so continue
  • Remove node 0, process its neighbors:
    • Neighbor 1: decrement in-degree from 1 to 0, add to queue
    • Neighbor 2: decrement in-degree from 2 to 1
  • Queue = [1]

Iteration 2:

  • Queue has exactly 1 element (node 1), so continue
  • Remove node 1, process its neighbor:
    • Neighbor 2: decrement in-degree from 1 to 0, add to queue
  • Queue = [2]

Iteration 3:

  • Queue has exactly 1 element (node 2), so continue
  • Remove node 2 (no neighbors to process)
  • Queue = []

Step 4: Verify Result

  • Queue is empty (length 0)
  • All nodes were processed with exactly one choice at each step
  • Return true - there is a unique shortest supersequence [1,2,3]

Counter-example: If we had sequences = [[1,2], [1,3]] instead:

  • After processing node 0, both nodes 1 and 2 would have in-degree 0
  • Queue = [1, 2] (length > 1)
  • The loop would break immediately
  • Return false - multiple valid orderings exist ([1,2,3] or [1,3,2])

Solution Implementation

1from collections import deque
2from itertools import pairwise
3from typing import List
4
5
6class Solution:
7    def sequenceReconstruction(
8        self, nums: List[int], sequences: List[List[int]]
9    ) -> bool:
10        """
11        Determines if the original sequence 'nums' can be uniquely reconstructed
12        from the given subsequences.
13      
14        Args:
15            nums: The original sequence to be reconstructed (1-indexed values)
16            sequences: List of subsequences that should reconstruct nums
17          
18        Returns:
19            True if nums is the only sequence that can be reconstructed from sequences
20        """
21        n = len(nums)
22      
23        # Build adjacency list for directed graph (0-indexed)
24        adjacency_list = [[] for _ in range(n)]
25      
26        # Track in-degree for each node (0-indexed)
27        in_degrees = [0] * n
28      
29        # Build the graph from all subsequences
30        for sequence in sequences:
31            # Process each consecutive pair in the sequence
32            for first_val, second_val in pairwise(sequence):
33                # Convert from 1-indexed to 0-indexed
34                from_node = first_val - 1
35                to_node = second_val - 1
36              
37                # Add edge from_node -> to_node
38                adjacency_list[from_node].append(to_node)
39                in_degrees[to_node] += 1
40      
41        # Initialize queue with all nodes having in-degree of 0
42        queue = deque(
43            node for node, in_degree in enumerate(in_degrees) if in_degree == 0
44        )
45      
46        # Process nodes in topological order
47        # The reconstruction is unique only if at each step there's exactly one choice
48        while len(queue) == 1:
49            current_node = queue.popleft()
50          
51            # Process all neighbors of current node
52            for neighbor in adjacency_list[current_node]:
53                in_degrees[neighbor] -= 1
54              
55                # If neighbor has no more dependencies, add to queue
56                if in_degrees[neighbor] == 0:
57                    queue.append(neighbor)
58      
59        # Reconstruction is valid and unique if queue is empty
60        # (all nodes processed with exactly one choice at each step)
61        return len(queue) == 0
62
1class Solution {
2    public boolean sequenceReconstruction(int[] nums, List<List<Integer>> sequences) {
3        int n = nums.length;
4      
5        // Array to store the in-degree of each node (number of incoming edges)
6        int[] inDegree = new int[n];
7      
8        // Adjacency list representation of the graph
9        // Each index represents a node, and the list contains its outgoing edges
10        List<Integer>[] adjacencyList = new List[n];
11      
12        // Initialize each adjacency list
13        Arrays.setAll(adjacencyList, index -> new ArrayList<>());
14      
15        // Build the directed graph from the sequences
16        for (List<Integer> sequence : sequences) {
17            for (int i = 1; i < sequence.size(); i++) {
18                // Convert to 0-indexed (subtract 1 from each value)
19                int fromNode = sequence.get(i - 1) - 1;
20                int toNode = sequence.get(i) - 1;
21              
22                // Add directed edge from fromNode to toNode
23                adjacencyList[fromNode].add(toNode);
24              
25                // Increment in-degree of the destination node
26                inDegree[toNode]++;
27            }
28        }
29      
30        // Queue for topological sort using Kahn's algorithm
31        Deque<Integer> queue = new ArrayDeque<>();
32      
33        // Add all nodes with in-degree 0 to the queue (starting nodes)
34        for (int i = 0; i < n; i++) {
35            if (inDegree[i] == 0) {
36                queue.offer(i);
37            }
38        }
39      
40        // Process nodes while there is exactly one node in the queue
41        // This ensures a unique topological ordering
42        while (queue.size() == 1) {
43            // Remove the current node from the queue
44            int currentNode = queue.poll();
45          
46            // Process all neighbors of the current node
47            for (int neighbor : adjacencyList[currentNode]) {
48                // Decrement in-degree of the neighbor
49                inDegree[neighbor]--;
50              
51                // If in-degree becomes 0, add to queue
52                if (inDegree[neighbor] == 0) {
53                    queue.offer(neighbor);
54                }
55            }
56        }
57      
58        // Return true if queue is empty (unique reconstruction exists)
59        // Return false if queue has multiple elements (multiple valid orderings)
60        return queue.isEmpty();
61    }
62}
63
1class Solution {
2public:
3    bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
4        int n = nums.size();
5      
6        // Initialize in-degree array for each node (0-indexed)
7        vector<int> inDegree(n, 0);
8      
9        // Build adjacency list representation of the graph
10        vector<vector<int>> graph(n);
11      
12        // Construct the directed graph from sequences
13        for (const auto& sequence : sequences) {
14            for (int i = 1; i < sequence.size(); ++i) {
15                // Convert to 0-indexed (assuming sequences contain 1-indexed values)
16                int from = sequence[i - 1] - 1;
17                int to = sequence[i] - 1;
18              
19                // Add edge from 'from' to 'to'
20                graph[from].push_back(to);
21                // Increment in-degree of 'to' node
22                ++inDegree[to];
23            }
24        }
25      
26        // Initialize queue for topological sort with all nodes having in-degree 0
27        queue<int> topoQueue;
28        for (int node = 0; node < n; ++node) {
29            if (inDegree[node] == 0) {
30                topoQueue.push(node);
31            }
32        }
33      
34        // Perform topological sort
35        // The reconstruction is unique only if at each step we have exactly one choice
36        while (topoQueue.size() == 1) {
37            // Get the only node available for processing
38            int currentNode = topoQueue.front();
39            topoQueue.pop();
40          
41            // Process all neighbors of current node
42            for (int neighbor : graph[currentNode]) {
43                // Decrement in-degree and add to queue if it becomes 0
44                if (--inDegree[neighbor] == 0) {
45                    topoQueue.push(neighbor);
46                }
47            }
48        }
49      
50        // If queue is empty, all nodes were processed with unique ordering
51        // If queue has more than 1 element, multiple valid orderings exist
52        return topoQueue.empty();
53    }
54};
55
1/**
2 * Determines if the given sequences can uniquely reconstruct the nums array
3 * using topological sorting to verify if there's exactly one valid ordering
4 * 
5 * @param nums - The target sequence to be reconstructed
6 * @param sequences - Array of subsequences used for reconstruction
7 * @returns true if nums is the only sequence that can be reconstructed from sequences
8 */
9function sequenceReconstruction(nums: number[], sequences: number[][]): boolean {
10    const sequenceLength: number = nums.length;
11  
12    // Build adjacency list for the directed graph
13    // Each index represents a node (0-indexed), containing its outgoing edges
14    const adjacencyList: number[][] = Array.from({ length: sequenceLength }, () => []);
15  
16    // Track the in-degree (number of incoming edges) for each node
17    const inDegree: number[] = Array(sequenceLength).fill(0);
18  
19    // Construct the graph from the given sequences
20    for (const sequence of sequences) {
21        for (let i = 1; i < sequence.length; i++) {
22            // Convert to 0-indexed and create edge from previous to current element
23            const fromNode: number = sequence[i - 1] - 1;
24            const toNode: number = sequence[i] - 1;
25          
26            adjacencyList[fromNode].push(toNode);
27            inDegree[toNode]++;
28        }
29    }
30  
31    // Initialize queue with all nodes having zero in-degree (starting nodes)
32    const queue: number[] = inDegree
33        .map((degree, index) => (degree === 0 ? index : -1))
34        .filter(node => node !== -1);
35  
36    // Process nodes using topological sort
37    // The reconstruction is unique only if we have exactly one node to process at each step
38    while (queue.length === 1) {
39        const currentNode: number = queue.pop()!;
40      
41        // Update in-degrees of neighboring nodes
42        for (const neighbor of adjacencyList[currentNode]) {
43            inDegree[neighbor]--;
44          
45            // Add to queue if all dependencies are resolved
46            if (inDegree[neighbor] === 0) {
47                queue.push(neighbor);
48            }
49        }
50    }
51  
52    // Reconstruction is valid and unique if all nodes were processed
53    // (queue should be empty after processing all nodes)
54    return queue.length === 0;
55}
56

Time and Space Complexity

Time Complexity: O(n + m)

The algorithm performs the following operations:

  • Building the graph: Iterating through all sequences and all adjacent pairs takes O(m) time, where m is the total number of edges (sum of lengths of all sequences minus the number of sequences)
  • Initializing the indegree array: O(n) time
  • Finding initial nodes with zero indegree: O(n) time
  • BFS/Topological sort: Each node is processed once (O(n)), and each edge is traversed once when updating indegrees (O(m))

Total time complexity: O(n + m)

Space Complexity: O(n + m)

The algorithm uses:

  • Graph adjacency list g: Stores all edges, requiring O(m) space
  • Indegree array indeg: Stores indegree for each node, requiring O(n) space
  • Queue q: In the worst case contains all nodes, requiring O(n) space

Total space complexity: O(n + m)

Where n is the number of nodes (length of nums) and m is the total number of edges across all sequences.

Common Pitfalls

1. Not Handling Duplicate Edges in the Graph

One critical pitfall is that the current implementation doesn't handle duplicate edges properly. When building the graph from subsequences, if the same edge appears multiple times (e.g., [1,2] appears in multiple subsequences), the in-degree gets incremented multiple times for the same relationship.

Problem Example:

nums = [1, 2, 3]
sequences = [[1, 2], [1, 2], [2, 3]]  # [1,2] appears twice

The current code would set in_degrees[1] to 2 instead of 1, leading to incorrect results.

Solution: Use a set to track edges and only increment in-degree once per unique edge:

def sequenceReconstruction(self, nums: List[int], sequences: List[List[int]]) -> bool:
    n = len(nums)
    adjacency_list = [[] for _ in range(n)]
    in_degrees = [0] * n
  
    # Track edges to avoid duplicates
    edges_seen = set()
  
    for sequence in sequences:
        for first_val, second_val in pairwise(sequence):
            from_node = first_val - 1
            to_node = second_val - 1
            edge = (from_node, to_node)
          
            # Only process each unique edge once
            if edge not in edges_seen:
                edges_seen.add(edge)
                adjacency_list[from_node].append(to_node)
                in_degrees[to_node] += 1
  
    # Rest of the code remains the same...

2. Not Validating Input Values Are Within Range

The code assumes all values in subsequences are valid (between 1 and n). Invalid values would cause index out of bounds errors.

Problem Example:

nums = [1, 2, 3]
sequences = [[1, 4]]  # 4 is out of range

Solution: Add validation before processing:

for sequence in sequences:
    for val in sequence:
        if val < 1 or val > n:
            return False  # Invalid value in subsequence
    # Continue with pairwise processing...

3. Not Checking if All Numbers Are Covered

The algorithm might return true even if not all numbers from nums appear in the subsequences, as it only checks the topological ordering of edges that exist.

Problem Example:

nums = [1, 2, 3]
sequences = [[1, 2]]  # 3 is never mentioned

Solution: Track which numbers appear in subsequences and verify all are present:

seen_numbers = set()
for sequence in sequences:
    seen_numbers.update(sequence)

if seen_numbers != set(range(1, n + 1)):
    return False  # Not all numbers are covered

4. Empty Subsequences Handling

The code doesn't explicitly handle empty subsequences, though pairwise() naturally handles them by producing no pairs.

Better Practice: Filter out empty subsequences explicitly for clarity:

sequences = [seq for seq in sequences if len(seq) > 0]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More