LeetCode #3435 — HARD

Frequencies of Shortest Supersequences

Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.

Solve on LeetCode
The Problem

Problem Statement

You are given an array of strings words. Find all shortest common supersequences (SCS) of words that are not permutations of each other.

A shortest common supersequence is a string of minimum length that contains each string in words as a subsequence.

Return a 2D array of integers freqs that represent all the SCSs. Each freqs[i] is an array of size 26, representing the frequency of each letter in the lowercase English alphabet for a single SCS. You may return the frequency arrays in any order.

Example 1:

Input: words = ["ab","ba"]

Output: [[1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]

Explanation:

The two SCSs are "aba" and "bab". The output is the letter frequencies for each one.

Example 2:

Input: words = ["aa","ac"]

Output: [[2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]

Explanation:

The two SCSs are "aac" and "aca". Since they are permutations of each other, keep only "aac".

Example 3:

Input: words = ["aa","bb","cc"]

Output: [[2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]

Explanation:

"aabbcc" and all its permutations are SCSs.

Constraints:

  • 1 <= words.length <= 256
  • words[i].length == 2
  • All strings in words will altogether be composed of no more than 16 unique lowercase letters.
  • All strings in words are unique.
Patterns Used

Roadmap

  1. Brute Force Baseline
  2. Core Insight
  3. Algorithm Walkthrough
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Study Demo
  7. Complexity Analysis
Step 01

Brute Force Baseline

Problem summary: You are given an array of strings words. Find all shortest common supersequences (SCS) of words that are not permutations of each other. A shortest common supersequence is a string of minimum length that contains each string in words as a subsequence. Return a 2D array of integers freqs that represent all the SCSs. Each freqs[i] is an array of size 26, representing the frequency of each letter in the lowercase English alphabet for a single SCS. You may return the frequency arrays in any order.

Baseline thinking

Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.

Pattern signal: Array · Bit Manipulation · Topological Sort

Example 1

["ab","ba"]

Example 2

["aa","ac"]

Example 3

["aa","bb","cc"]
Step 02

Core Insight

What unlocks the optimal approach

  • Each SCS contains at most 2 occurrences of each character. Why?
  • Construct every subset of possible characters (1 or 2).
  • Check if a supersequence could be constructed using Topological Sort.
Interview move: turn each hint into an invariant you can check after every iteration/recursion step.
Step 03

Algorithm Walkthrough

Iteration Checklist

  1. Define state (indices, window, stack, map, DP cell, or recursion frame).
  2. Apply one transition step and update the invariant.
  3. Record answer candidate when condition is met.
  4. Continue until all input is consumed.
Use the first example testcase as your mental trace to verify each transition.
Step 04

Edge Cases

Minimum Input
Single element / shortest valid input
Validate boundary behavior before entering the main loop or recursion.
Duplicates & Repeats
Repeated values / repeated states
Decide whether duplicates should be merged, skipped, or counted explicitly.
Extreme Constraints
Largest constraint values
Re-check complexity target against constraints to avoid time-limit issues.
Invalid / Corner Shape
Empty collections, zeros, or disconnected structures
Handle special-case structure before the core algorithm path.
Step 05

Full Annotated Code

Source-backed implementations are provided below for direct study and interview prep.

// Accepted solution for LeetCode #3435: Frequencies of Shortest Supersequences
enum State { INIT, VISITING, VISITED }

class Solution {
  public List<List<Integer>> supersequences(String[] words) {
    List<List<Integer>> ans = new ArrayList<>();
    List<int[]> edges = getEdges(words);
    List<Integer> nodes = getNodes(edges);
    int[] letterToIndex = getLetterToIndex(nodes);
    List<Integer>[] graph = new List[nodes.size()];

    for (int i = 0; i < nodes.size(); i++)
      graph[i] = new ArrayList<>();

    for (int[] edge : edges) {
      final int u = edge[0];
      final int v = edge[1];
      graph[letterToIndex[u]].add(letterToIndex[v]);
    }

    for (List<Integer> doubledSubset : getMinimumSubsets(graph)) {
      int[] freq = new int[26];
      for (final int letter : nodes)
        freq[letter] = 1;
      for (final int index : doubledSubset)
        freq[nodes.get(index)] = 2;
      ans.add(Arrays.stream(freq).boxed().collect(Collectors.toList()));
    }

    return ans;
  }

  // Returns a list of the minimum subsets of nodes that do not create a cycle
  // when skipped.
  private List<List<Integer>> getMinimumSubsets(List<Integer>[] graph) {
    final int n = graph.length;
    List<List<Integer>> res = new ArrayList<>();

    for (int subsetSize = 0; subsetSize <= n; ++subsetSize) {
      boolean[] combination = new boolean[n];
      Arrays.fill(combination, n - subsetSize, n, true);
      do {
        List<Integer> doubledSubset = new ArrayList<>();
        for (int i = 0; i < n; i++)
          if (combination[i])
            doubledSubset.add(i);
        if (!hasCycleSkipping(graph, new HashSet<>(doubledSubset)))
          res.add(doubledSubset);
      } while (nextPermutation(combination));
      if (!res.isEmpty())
        return res;
    }
    return res;
  }

  // Returns true if there is a cycle in the `graph` when skipping any edges
  // whose both endpoints are in `doubledSubset`.
  private boolean hasCycleSkipping(List<Integer>[] graph, Set<Integer> doubledSubset) {
    State[] states = new State[graph.length];
    for (int i = 0; i < graph.length; ++i)
      if (hasCycle(graph, i, states, doubledSubset))
        return true;
    return false;
  }

  private boolean hasCycle(List<Integer>[] graph, int u, State[] states,
                           Set<Integer> doubledSubset) {
    if (states[u] == State.VISITING)
      return true;
    if (states[u] == State.VISITED)
      return false;
    states[u] = State.VISITING;
    if (!doubledSubset.contains(u))
      for (final int v : graph[u])
        if (!doubledSubset.contains(v) && hasCycle(graph, v, states, doubledSubset))
          return true;
    states[u] = State.VISITED;
    return false;
  }

  private List<int[]> getEdges(String[] words) {
    List<int[]> edges = new ArrayList<>();
    for (final String word : words)
      edges.add(new int[] {word.charAt(0) - 'a', word.charAt(1) - 'a'});
    return edges;
  }

  private List<Integer> getNodes(List<int[]> edges) {
    TreeSet<Integer> nodes = new TreeSet<>();
    for (int[] edge : edges) {
      final int u = edge[0];
      final int v = edge[1];
      nodes.add(u);
      nodes.add(v);
    }
    return new ArrayList<>(nodes);
  }

  private int[] getLetterToIndex(List<Integer> nodes) {
    int[] letterToIndex = new int[26];
    for (int i = 0; i < nodes.size(); ++i)
      letterToIndex[nodes.get(i)] = i;
    return letterToIndex;
  }

  private boolean nextPermutation(boolean[] arr) {
    final int n = arr.length;

    // From back to front, find the first false followed by true
    int i;
    for (i = n - 2; i >= 0; --i)
      if (!arr[i] && arr[i + 1])
        break;

    // If no such pair found, we've reached the last permutation
    if (i < 0)
      return false;

    // From back to front, find the first true to swap with arr[i].
    for (int j = n - 1; j > i; --j)
      if (arr[j] && !arr[i]) {
        swap(arr, i, j);
        break;
      }

    // Reverse arr[i + 1..n - 1].
    reverse(arr, i + 1, n - 1);
    return true;
  }

  private void reverse(boolean[] arr, int l, int r) {
    while (l < r)
      swap(arr, l++, r--);
  }

  private void swap(boolean[] arr, int i, int j) {
    boolean temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
}
Step 06

Interactive Study Demo

Use this to step through a reusable interview workflow for this problem.

Press Step or Run All to begin.
Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

Approach Breakdown

SORT + SCAN
O(n log n) time
O(n) space

Sort the array in O(n log n), then scan for the missing or unique element by comparing adjacent pairs. Sorting requires O(n) auxiliary space (or O(1) with in-place sort but O(n log n) time remains). The sort step dominates.

BIT MANIPULATION
O(n) time
O(1) space

Bitwise operations (AND, OR, XOR, shifts) are O(1) per operation on fixed-width integers. A single pass through the input with bit operations gives O(n) time. The key insight: XOR of a number with itself is 0, which eliminates duplicates without extra space.

Shortcut: Bit operations are O(1). XOR cancels duplicates. Single pass → O(n) time, O(1) space.
Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Off-by-one on range boundaries

Wrong move: Loop endpoints miss first/last candidate.

Usually fails on: Fails on minimal arrays and exact-boundary answers.

Fix: Re-derive loops from inclusive/exclusive ranges before coding.