DP — Find the Shortest Superstring
↗ LeetCode: Find the Shortest Superstring · Hard · pattern chapter →
Return shortest string containing every given word as substring.
Example 1 — words=["alex","loves","leetcode"] → "alexlovesleetcode" (no overlaps merge) Example 2 — words=["catg","ctaagt","gcta","ttca","atgcatc"] → "gctaagttcatgcatc" (heavy overlaps) Example 3 — words=["a"] → "a"
Constraints — 1 ≤ n ≤ 12; each word ≤ 20 chars. Brute permutation is O(n!·n·L) — 12!·12·20 ≈ 1.1·10¹¹ ops = TLE. Bitmask DP is O(n²·2ⁿ) ≤ 144·4096 = 6·10⁵ ops.
Interview timer00:00
Approach 1 — Brute permutation
Intuition. Try every ordering, concat with max overlap between adjacent pair. Return shortest. n! orderings.
Complexity — Time O(n!·n·L); Space O(n). TLE past n=9. In an interview say "orderings collapse under bitmask (visited-set, last-word) → classic TSP DP → O(n²·2ⁿ)."
Approach 2 — Bitmask TSP-style DP + overlap precompute (canonical)
Insight.
overlap[i][j]= max suffix ofwords[i]that is prefix ofwords[j].dp[mask][i]= shortest length ending at word i after using set mask.- Reconstruct path via predecessor tracking.
String shortestSuperstring(String[] words) {
int n = words.length;
int[][] ov = new int[n][n];
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (i != j) ov[i][j] = computeOverlap(words[i], words[j]);
int full = 1 << n;
int[][] dp = new int[full][n];
int[][] par = new int[full][n];
for (int[] r : dp) Arrays.fill(r, Integer.MAX_VALUE / 2);
for (int i = 0; i < n; i++) dp[1 << i][i] = words[i].length();
for (int mask = 1; mask < full; mask++)
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) == 0) continue;
for (int j = 0; j < n; j++) {
if ((mask & (1 << j)) != 0) continue;
int cand = dp[mask][i] + words[j].length() - ov[i][j];
int nMask = mask | (1 << j);
if (cand < dp[nMask][j]) { dp[nMask][j] = cand; par[nMask][j] = i; }
}
}
int last = 0;
for (int i = 1; i < n; i++) if (dp[full-1][i] < dp[full-1][last]) last = i;
int[] order = new int[n];
int mask = full - 1;
for (int k = n - 1; k >= 0; k--) { order[k] = last; int prev = par[mask][last]; mask ^= 1 << last; last = prev; }
StringBuilder sb = new StringBuilder(words[order[0]]);
for (int k = 1; k < n; k++) sb.append(words[order[k]].substring(ov[order[k-1]][order[k]]));
return sb.toString();
}
int computeOverlap(String a, String b) {
int max = Math.min(a.length(), b.length());
for (int k = max; k > 0; k--) if (a.endsWith(b.substring(0, k))) return k;
return 0;
}Complexity — Time O(n² · 2ⁿ); Space O(n · 2ⁿ). Say aloud in an interview: "canonical bitmask-TSP — same skeleton as Shortest Path Visiting All Nodes."
Try it yourself
Complexity summary
| Approach | Time | Space | Grade |
|---|---|---|---|
| Brute permutation | O(n!·n·L) | O(n) | TLE past n=9 |
| Bitmask TSP DP | O(n² · 2ⁿ) | O(n · 2ⁿ) | Canonical |
When to use which
- Small n TSP-style → bitmask DP.
- Approximation → greedy longest-overlap merge.