3571. Find the Shortest Superstring II 🔒
Problem Description
You are given two strings, s1
and s2
. Return the shortest possible string that contains both s1
and s2
as substrings. If there are multiple valid answers, return any one of them.
A substring is a contiguous sequence of characters within a string.
To solve this, construct the shortest string that contains both s1
and s2
by maximizing overlap. Try to overlap the suffix of one string with the prefix of the other. Consider these scenarios:
- If
s1
is already a substring ofs2
, thens2
itself is a valid answer. The same applies ifs2
is a substring ofs1
. - Try to overlap the end of
s1
with the beginning ofs2
as much as possible and concatenate the rest. - Similarly, try to overlap the end of
s2
with the beginning ofs1
. - If there is no overlap, simply concatenate
s1
ands2
.
The answer should be the shortest string containing both as substrings. If multiple shortest strings exist, you can return any of them.
Intuition
To build the shortest string that contains both s1
and s2
as substrings, try to merge them with as much overlap as possible. Since a substring is a continuous part of a string, look for places where the end of one string matches the beginning of the other. For example, if the suffix of s1
matches the prefix of s2
, those characters don’t need to appear twice. The same idea works in reverse.
If neither string contains the other and there’s no overlap, just stick them together (i.e., s1 + s2
). But if any overlap exists — like some ending of s1
is the same as the beginning of s2
— use that to make the merged string shorter. The closer they are “lined up,” the shorter your result will be. This way, the problem becomes searching for the maximum length of overlap between the two strings in both possible orders.
Solution Approach
To find the shortest string containing both s1
and s2
as substrings, follow these steps:
-
Check if either string contains the other:
- If
s1
is already a substring ofs2
, returns2
. - If
s2
is already a substring ofs1
, returns1
. - This case gives the shortest possible answer immediately.
- If
-
Find maximum suffix-prefix overlap:
- Try to overlap the end of
s1
with the start ofs2
as much as possible. - For each suffix of
s1
, check if it matches the corresponding prefix ofs2
. - If
s2.startswith(s1[i:])
isTrue
, then the overlap iss1[i:]
, and you can combine them ass1[:i] + s2
. - This reduces the total length by the length of the overlap.
- Try to overlap the end of
-
Find maximum prefix-suffix overlap (reverse order):
- Also check if the beginning of
s1
matches the end ofs2
. - For each prefix of
s1
, check if it is equal to the corresponding suffix ofs2
. - If
s2.endswith(s1[:m - i])
isTrue
, then overlap iss1[:m - i]
, and you can combine ass2 + s1[m - i:]
.
- Also check if the beginning of
-
No overlap:
- If neither of the above approaches yielded an overlap, just return the two strings concatenated:
s1 + s2
.
- If neither of the above approaches yielded an overlap, just return the two strings concatenated:
This process ensures you always get the shortest possible result because it checks all ways the strings might overlap, and stops at the largest overlap.
The major pattern here is checking overlaps using string matching, by sliding through potential suffixes and prefixes of both strings. This leads to a time complexity of O(m * n)
, where m
and n
are the lengths of the two strings.
Here’s the basic logic shown in Python-style pseudocode:
def shortestSuperstring(s1, s2): if s1 in s2: return s2 if s2 in s1: return s1 # Check for suffix-prefix overlap for i in range(len(s1)): if s2.startswith(s1[i:]): return s1[:i] + s2 # Check for prefix-suffix overlap for i in range(len(s1)): if s2.endswith(s1[:len(s1)-i]): return s2 + s1[len(s1)-i:] # No overlap return s1 + s2
This approach is efficient and directly matches the described logic in the solution code.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the approach using an example.
Suppose:
s1 = "abcde"
s2 = "cdefg"
Step 1: Check for direct containment
"abcde"
is not a substring of"cdefg"
"cdefg"
is not a substring of"abcde"
- So, move to the next step.
Step 2: Find maximum suffix-prefix overlap (s1
suffix matches s2
prefix)
Check overlaps by sliding through the suffixes of s1
:
i = 0
: Does"cdefg"
start with"abcde"
? Noi = 1
: Does"cdefg"
start with"bcde"
? Noi = 2
: Does"cdefg"
start with"cde"
? Yes! Overlap is"cde"
(length 3)
So we can merge by taking the non-overlapping prefix of s1
and the entirety of s2
:
- Result:
"ab"
(froms1[:2]
) +"cdefg"
="abcdefg"
Step 3: Check maximum prefix-suffix overlap (reverse order)
We already found an overlap, but the approach would also check the other way:
Check if "abcde"
starts with any suffix of "cdefg"
:
i = 0
: Does"abcde"
start with"cdefg"
? Noi = 1
: Does"abcde"
start with"defg"
? Noi = 2
: Does"abcde"
start with"efg"
? No- ... No better overlap found in this direction.
Step 4: If no overlap was found, concatenate: Not needed here; we've already found the shortest possible superstring.
Summary:
The merged shortest string is "abcdefg"
.
This walk-through demonstrates checking for substring containment, sliding suffix-prefix overlaps, and reversing the direction—all to produce the minimal superstring.
Solution Implementation
1class Solution:
2 def shortestSuperstring(self, s1: str, s2: str) -> str:
3 # Get lengths of both strings
4 len1, len2 = len(s1), len(s2)
5
6 # Ensure s1 is not longer than s2 for consistency
7 if len1 > len2:
8 return self.shortestSuperstring(s2, s1)
9
10 # If s1 is already a substring of s2, just return s2
11 if s1 in s2:
12 return s2
13
14 # Try to overlap s1 suffix with s2 prefix
15 for i in range(len1):
16 # If s2 starts with the suffix of s1 from index i
17 if s2.startswith(s1[i:]):
18 # Merge by avoiding overlap part
19 return s1[:i] + s2
20
21 # If s2 ends with the prefix of s1 up to len1 - i
22 if s2.endswith(s1[: len1 - i]):
23 # Merge by avoiding overlap part
24 return s2 + s1[len1 - i :]
25
26 # If no overlap, return simple concatenation
27 return s1 + s2
28
1class Solution {
2 public String shortestSuperstring(String s1, String s2) {
3 int len1 = s1.length();
4 int len2 = s2.length();
5
6 // Swap to always handle the shorter string first for efficiency
7 if (len1 > len2) {
8 return shortestSuperstring(s2, s1);
9 }
10
11 // If one string contains the other, return the longer one
12 if (s2.contains(s1)) {
13 return s2;
14 }
15
16 // Try to merge strings with maximal overlap
17 for (int i = 0; i < len1; i++) {
18 // Check if s2 starts with a suffix of s1 (maximal prefix-suffix overlap)
19 if (s2.startsWith(s1.substring(i))) {
20 // Combine non-overlapping prefix of s1 with s2
21 return s1.substring(0, i) + s2;
22 }
23 // Check if s2 ends with a prefix of s1 (maximal suffix-prefix overlap)
24 if (s2.endsWith(s1.substring(0, len1 - i))) {
25 // Combine s2 with the non-overlapping suffix of s1
26 return s2 + s1.substring(len1 - i);
27 }
28 }
29
30 // No overlap found; simply concatenate
31 return s1 + s2;
32 }
33}
34
1class Solution {
2public:
3 // Returns the shortest superstring of s1 and s2 by maximizing the overlap
4 string shortestSuperstring(string s1, string s2) {
5 int len1 = s1.size();
6 int len2 = s2.size();
7
8 // Always call with shorter string first for consistency
9 if (len1 > len2) {
10 return shortestSuperstring(s2, s1);
11 }
12
13 // If s1 is a substring of s2, s2 itself is the result
14 if (s2.find(s1) != string::npos) {
15 return s2;
16 }
17
18 // Try to overlap s1's suffix with s2's prefix
19 for (int i = 0; i < len1; ++i) {
20 // Check for overlap: does s2 start with s1.substr(i)?
21 if (s2.find(s1.substr(i)) == 0) {
22 // Combine the non-overlapping prefix of s1 with s2
23 return s1.substr(0, i) + s2;
24 }
25 // Check for overlap: does s2 end with s1's prefix?
26 if (s2.rfind(s1.substr(0, len1 - i)) == len2 - (len1 - i)) {
27 // Combine s2 with the non-overlapping suffix of s1
28 return s2 + s1.substr(len1 - i);
29 }
30 }
31
32 // If there's no overlap, simply concatenate
33 return s1 + s2;
34 }
35};
36
1/**
2 * Returns the shortest superstring that contains both s1 and s2 as substrings.
3 * @param s1 - First input string.
4 * @param s2 - Second input string.
5 * @returns The shortest superstring containing both s1 and s2.
6 */
7function shortestSuperstring(s1: string, s2: string): string {
8 const len1 = s1.length;
9 const len2 = s2.length;
10
11 // Ensure s1 is not longer than s2, for consistent processing.
12 if (len1 > len2) {
13 return shortestSuperstring(s2, s1);
14 }
15
16 // If s1 is already a substring of s2, s2 is the answer.
17 if (s2.includes(s1)) {
18 return s2;
19 }
20
21 // Try to find the maximal overlap by sliding s1 over s2.
22 for (let i = 0; i < len1; i++) {
23 // If s2 starts with the suffix of s1
24 if (s2.startsWith(s1.slice(i))) {
25 // Merge the non-overlapping prefix of s1 with s2.
26 return s1.slice(0, i) + s2;
27 }
28 // If s2 ends with the prefix of s1
29 if (s2.endsWith(s1.slice(0, len1 - i))) {
30 // Merge s2 with the non-overlapping suffix of s1.
31 return s2 + s1.slice(len1 - i);
32 }
33 }
34
35 // If no overlap, simply concatenate the strings.
36 return s1 + s2;
37}
38
Time and Space Complexity
-
Time Complexity: The most significant loops in the code are the two
for
loops which each iterate at mostm
times (m = len(s1)
). Inside each loop, string slicing and thestartswith
orendswith
operations are called, both of which take up toO(n)
time (n = max(len(s1), len(s2))
). Thus, the time complexity per loop isO(m * n)
. Sincem <= n
, this isO(n^2)
overall. -
Space Complexity: The space required is mainly for temporary substrings created during slicing and concatenation, each up to length
n
, resulting in a space complexity ofO(n)
.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!