Facebook Pixel

2800. Shortest String That Contains Three Strings

MediumGreedyStringEnumeration
Leetcode Link

Problem Description

You are given three strings a, b, and c. Your task is to find the shortest possible string that contains all three strings as substrings.

A substring is a contiguous sequence of characters within a string. For example, "abc" is a substring of "xabcy" but not of "xabyc".

The goal is to create a string that:

  1. Contains string a as a substring
  2. Contains string b as a substring
  3. Contains string c as a substring
  4. Has the minimum possible length

If there are multiple strings with the same minimum length that satisfy these conditions, return the lexicographically smallest one.

Lexicographical ordering means dictionary order - a string is lexicographically smaller than another string of the same length if at the first position where they differ, it has a character that comes earlier in the alphabet.

For example:

  • If a = "abc", b = "bca", and c = "aaa", one possible answer could be "aaabca" which contains all three strings as substrings:
    • "aaa" is present starting at index 0
    • "abc" is present starting at index 2
    • "bca" is present starting at index 3

The solution involves trying different ways to merge the strings by finding overlaps between them and choosing the arrangement that produces the shortest result, with ties broken by lexicographical order.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that when we want to combine multiple strings into one while minimizing the total length, we should look for overlapping parts between strings. For instance, if we have "abc" and "bcd", instead of concatenating them as "abcbcd" (length 7), we can overlap them as "abcd" (length 4) since "bc" is common.

When dealing with three strings, the order in which we merge them matters. Consider merging "abc", "bca", and "cab". If we merge "abc" with "bca" first, we get "abca". Then merging with "cab" gives us "abcab". But if we started with "bca" and "cab", we'd get "bcab", and then merging with "abc" gives us "abcab" or potentially something different depending on overlaps.

Since there are only 3 strings, we can try all possible orderings - there are exactly 3! = 6 permutations. For each permutation, we merge the strings in that order and keep track of the shortest result.

The merging strategy for two strings works as follows:

  1. First check if one string is already contained in the other - if so, just return the longer string
  2. Otherwise, find the maximum overlap: check if the end of the first string matches the beginning of the second string
  3. Start with the largest possible overlap and work down to find a match
  4. If no overlap exists, simply concatenate the strings

By trying all 6 possible orderings and applying this merging strategy, we're guaranteed to find the optimal solution. Among all solutions with minimum length, we pick the lexicographically smallest one.

Learn more about Greedy patterns.

Solution Approach

The solution uses a helper function f(s, t) to merge two strings optimally, and then applies this function across all permutations of the three input strings.

Helper Function f(s, t) - Merging Two Strings:

  1. Check containment: If string s is already contained in t, return t. If t is contained in s, return s. This handles cases where one string is a substring of another.

  2. Find maximum overlap: If neither contains the other, look for the longest overlap between the end of s and the beginning of t:

    • Start with the minimum of the two string lengths (since overlap can't exceed the shorter string)
    • Check progressively smaller overlaps from min(m, n) down to 1
    • For each overlap length i, check if the last i characters of s match the first i characters of t: s[-i:] == t[:i]
    • If a match is found, return s + t[i:] (string s followed by the non-overlapping part of t)
  3. No overlap: If no overlap exists, concatenate the strings: s + t

Main Algorithm:

  1. Generate all permutations: Use permutations((a, b, c)) to generate all 6 possible orderings of the three strings.

  2. Process each permutation: For each ordering (a, b, c):

    • First merge a and b: temp = f(a, b)
    • Then merge the result with c: s = f(temp, c)
    • This gives us the merged string for this particular ordering
  3. Track the optimal result: Compare each merged string s with the current best answer:

    • If ans is empty (first iteration), set ans = s
    • If len(s) < len(ans), update ans to s (shorter is better)
    • If len(s) == len(ans) and s < ans, update ans to s (same length but lexicographically smaller)
  4. Return the result: After checking all 6 permutations, return the optimal string stored in ans.

The time complexity is O(nΒ²) for each merge operation (where n is the length of the strings), and since we perform a constant number of merges (6 permutations Γ— 2 merges each), the overall complexity remains manageable for typical string lengths.

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 a concrete example where a = "abc", b = "bca", and c = "aab".

Step 1: Understanding the merge function f(s, t)

First, let's see how the merge function works with f("abc", "bca"):

  • Check if "abc" contains "bca" β†’ No
  • Check if "bca" contains "abc" β†’ No
  • Look for overlap between end of "abc" and start of "bca":
    • Check if last 3 chars of "abc" ("abc") match first 3 chars of "bca" ("bca") β†’ No
    • Check if last 2 chars of "abc" ("bc") match first 2 chars of "bca" ("bc") β†’ Yes!
    • Return "abc" + "bca"[2:] = "abc" + "a" = "abca"

Step 2: Try all 6 permutations

Let's trace through each permutation:

  1. Order (a, b, c) = ("abc", "bca", "aab")

    • Merge "abc" and "bca": f("abc", "bca") = "abca" (as shown above)
    • Merge "abca" and "aab": f("abca", "aab")
      • "abca" contains "aab"? No. "aab" contains "abca"? No
      • Check overlaps: "a" at end of "abca" matches "a" at start of "aab"
      • Result: "abca" + "ab" = "abcaab" (length 6)
  2. Order (a, c, b) = ("abc", "aab", "bca")

    • Merge "abc" and "aab": f("abc", "aab") = "aabc" (overlap "ab")
    • Merge "aabc" and "bca": f("aabc", "bca") = "aabca" (overlap "bc")
    • Result: "aabca" (length 5)
  3. Order (b, a, c) = ("bca", "abc", "aab")

    • Merge "bca" and "abc": f("bca", "abc") = "bcabc" (overlap "a")
    • Merge "bcabc" and "aab": f("bcabc", "aab") = "aabcabc" (no overlap)
    • Result: "aabcabc" (length 7)
  4. Order (b, c, a) = ("bca", "aab", "abc")

    • Merge "bca" and "aab": f("bca", "aab") = "bcaaab" (no overlap)
    • Merge "bcaaab" and "abc": f("bcaaab", "abc") = "bcaaabc" (overlap "ab")
    • Result: "bcaaabc" (length 7)
  5. Order (c, a, b) = ("aab", "abc", "bca")

    • Merge "aab" and "abc": f("aab", "abc") = "aabc" (overlap "ab")
    • Merge "aabc" and "bca": f("aabc", "bca") = "aabca" (overlap "bc")
    • Result: "aabca" (length 5)
  6. Order (c, b, a) = ("aab", "bca", "abc")

    • Merge "aab" and "bca": f("aab", "bca") = "aabca" (overlap "b")
    • Merge "aabca" and "abc": f("aabca", "abc")
      • "aabca" already contains "abc" (at position 1)
      • Result: "aabca" (length 5)

Step 3: Select the optimal result

From all permutations:

  • Length 5: "aabca" (appears 3 times)
  • Length 6: "abcaab"
  • Length 7: "aabcabc", "bcaaabc"

The shortest length is 5, and all three occurrences give us "aabca". We can verify this contains all three strings:

  • "abc" appears at index 1: "aabca"
  • "bca" appears at index 2: "aabca"
  • "aab" appears at index 0: "aabca"

Therefore, the answer is "aabca".

Solution Implementation

1from itertools import permutations
2from typing import List
3
4class Solution:
5    def minimumString(self, a: str, b: str, c: str) -> str:
6        """
7        Find the minimum string that contains all three given strings as substrings.
8        If multiple solutions exist, return the lexicographically smallest one.
9      
10        Args:
11            a: First string
12            b: Second string  
13            c: Third string
14          
15        Returns:
16            The minimum string containing all three strings as substrings
17        """
18      
19        def merge_two_strings(first: str, second: str) -> str:
20            """
21            Merge two strings to create the shortest string containing both as substrings.
22          
23            Args:
24                first: The first string to merge
25                second: The second string to merge
26              
27            Returns:
28                The shortest merged string containing both input strings
29            """
30            # If first string is already contained in second, return second
31            if first in second:
32                return second
33          
34            # If second string is already contained in first, return first
35            if second in first:
36                return first
37          
38            first_len, second_len = len(first), len(second)
39          
40            # Try to find the maximum overlap where end of first matches start of second
41            for overlap_length in range(min(first_len, second_len), 0, -1):
42                if first[-overlap_length:] == second[:overlap_length]:
43                    # Found overlap, concatenate first with non-overlapping part of second
44                    return first + second[overlap_length:]
45          
46            # No overlap found, concatenate both strings completely
47            return first + second
48      
49        result = ""
50      
51        # Try all permutations of the three strings to find the optimal order
52        for string1, string2, string3 in permutations((a, b, c)):
53            # First merge string1 and string2, then merge the result with string3
54            merged_string = merge_two_strings(merge_two_strings(string1, string2), string3)
55          
56            # Update result if this is the first permutation or if we found a better solution
57            # Better means: shorter length, or same length but lexicographically smaller
58            if (result == "" or 
59                len(merged_string) < len(result) or 
60                (len(merged_string) == len(result) and merged_string < result)):
61                result = merged_string
62      
63        return result
64
1class Solution {
2    /**
3     * Find the minimum string that contains all three input strings as substrings.
4     * If multiple solutions exist, return the lexicographically smallest one.
5     * 
6     * @param a First input string
7     * @param b Second input string
8     * @param c Third input string
9     * @return The minimum string containing all three strings
10     */
11    public String minimumString(String a, String b, String c) {
12        // Store the three strings in an array for easier access
13        String[] strings = {a, b, c};
14      
15        // All possible permutations of indices [0, 1, 2]
16        int[][] permutations = {
17            {0, 1, 2}, {0, 2, 1}, {1, 0, 2}, 
18            {1, 2, 0}, {2, 1, 0}, {2, 0, 1}
19        };
20      
21        String result = "";
22      
23        // Try all permutations to find the optimal order of merging
24        for (int[] permutation : permutations) {
25            int firstIndex = permutation[0];
26            int secondIndex = permutation[1];
27            int thirdIndex = permutation[2];
28          
29            // Merge first two strings, then merge the result with the third
30            String mergedFirstTwo = f(strings[firstIndex], strings[secondIndex]);
31            String mergedAll = f(mergedFirstTwo, strings[thirdIndex]);
32          
33            // Update result if current merged string is better
34            // Better means: shorter length, or same length but lexicographically smaller
35            if ("".equals(result) || 
36                mergedAll.length() < result.length() || 
37                (mergedAll.length() == result.length() && mergedAll.compareTo(result) < 0)) {
38                result = mergedAll;
39            }
40        }
41      
42        return result;
43    }
44  
45    /**
46     * Merge two strings to create the shortest string that contains both as substrings.
47     * 
48     * @param s First string
49     * @param t Second string
50     * @return The shortest merged string containing both s and t
51     */
52    private String f(String s, String t) {
53        // If s already contains t, return s
54        if (s.contains(t)) {
55            return s;
56        }
57      
58        // If t already contains s, return t
59        if (t.contains(s)) {
60            return t;
61        }
62      
63        int sLength = s.length();
64        int tLength = t.length();
65      
66        // Try to find the maximum overlap between end of s and beginning of t
67        for (int overlapLength = Math.min(sLength, tLength); overlapLength > 0; overlapLength--) {
68            // Check if the last 'overlapLength' characters of s match 
69            // the first 'overlapLength' characters of t
70            if (s.substring(sLength - overlapLength).equals(t.substring(0, overlapLength))) {
71                // Merge with overlap
72                return s + t.substring(overlapLength);
73            }
74        }
75      
76        // No overlap found, concatenate the strings
77        return s + t;
78    }
79}
80
1class Solution {
2public:
3    string minimumString(string a, string b, string c) {
4        // Store the three input strings in a vector
5        vector<string> strings = {a, b, c};
6      
7        // All possible permutations of indices [0, 1, 2]
8        vector<vector<int>> permutations = {
9            {0, 1, 2}, {0, 2, 1}, {1, 0, 2}, 
10            {1, 2, 0}, {2, 1, 0}, {2, 0, 1}
11        };
12      
13        string result = "";
14      
15        // Try all permutations to find the minimum string
16        for (auto& perm : permutations) {
17            int firstIdx = perm[0];
18            int secondIdx = perm[1];
19            int thirdIdx = perm[2];
20          
21            // Merge strings in the current permutation order
22            // First merge strings[firstIdx] and strings[secondIdx], then merge with strings[thirdIdx]
23            string merged = mergeStrings(
24                mergeStrings(strings[firstIdx], strings[secondIdx]), 
25                strings[thirdIdx]
26            );
27          
28            // Update result if current merged string is better
29            // Better means: shorter length, or same length but lexicographically smaller
30            if (result.empty() || 
31                merged.size() < result.size() || 
32                (merged.size() == result.size() && merged < result)) {
33                result = merged;
34            }
35        }
36      
37        return result;
38    }
39
40private:
41    // Merge two strings to create the shortest string containing both as substrings
42    string mergeStrings(string str1, string str2) {
43        // If str1 already contains str2, return str1
44        if (str1.find(str2) != string::npos) {
45            return str1;
46        }
47      
48        // If str2 already contains str1, return str2
49        if (str2.find(str1) != string::npos) {
50            return str2;
51        }
52      
53        int len1 = str1.size();
54        int len2 = str2.size();
55      
56        // Find the maximum overlap between suffix of str1 and prefix of str2
57        for (int overlapLen = min(len1, len2); overlapLen > 0; --overlapLen) {
58            // Check if the last 'overlapLen' characters of str1 match 
59            // the first 'overlapLen' characters of str2
60            if (str1.substr(len1 - overlapLen) == str2.substr(0, overlapLen)) {
61                // Merge by appending the non-overlapping part of str2 to str1
62                return str1 + str2.substr(overlapLen);
63            }
64        }
65      
66        // No overlap found, concatenate the strings
67        return str1 + str2;
68    }
69};
70
1/**
2 * Finds the minimum string that contains all three input strings as substrings
3 * @param a - First input string
4 * @param b - Second input string
5 * @param c - Third input string
6 * @returns The lexicographically smallest string that contains all three strings
7 */
8function minimumString(a: string, b: string, c: string): string {
9    /**
10     * Merges two strings to create the shortest string containing both
11     * @param source - First string to merge
12     * @param target - Second string to merge
13     * @returns Merged string with minimum length
14     */
15    const mergeStrings = (source: string, target: string): string => {
16        // If source already contains target, return source
17        if (source.includes(target)) {
18            return source;
19        }
20      
21        // If target already contains source, return target
22        if (target.includes(source)) {
23            return target;
24        }
25      
26        const sourceLength = source.length;
27        const targetLength = target.length;
28      
29        // Try to find overlapping suffix of source with prefix of target
30        for (let overlapLength = Math.min(sourceLength, targetLength); overlapLength > 0; --overlapLength) {
31            // Check if the last 'overlapLength' chars of source match first 'overlapLength' chars of target
32            if (source.slice(-overlapLength) === target.slice(0, overlapLength)) {
33                // Merge by appending non-overlapping part of target to source
34                return source + target.slice(overlapLength);
35            }
36        }
37      
38        // No overlap found, concatenate both strings
39        return source + target;
40    };
41  
42    // Array containing all three input strings
43    const strings: string[] = [a, b, c];
44  
45    // All possible permutations of indices [0, 1, 2]
46    const permutations: number[][] = [
47        [0, 1, 2],
48        [0, 2, 1],
49        [1, 0, 2],
50        [1, 2, 0],
51        [2, 0, 1],
52        [2, 1, 0],
53    ];
54  
55    // Initialize result string
56    let result = '';
57  
58    // Try all permutations to find the optimal merging order
59    for (const [firstIndex, secondIndex, thirdIndex] of permutations) {
60        // Merge strings in the current permutation order
61        const mergedString = mergeStrings(
62            mergeStrings(strings[firstIndex], strings[secondIndex]), 
63            strings[thirdIndex]
64        );
65      
66        // Update result if current merged string is better (shorter or lexicographically smaller)
67        if (result === '' || 
68            mergedString.length < result.length || 
69            (mergedString.length === result.length && mergedString < result)) {
70            result = mergedString;
71        }
72    }
73  
74    return result;
75}
76

Time and Space Complexity

Time Complexity: O(n^2)

The algorithm iterates through all 6 permutations of the three strings (constant factor). For each permutation, it calls function f twice. Within function f:

  • The substring checks s in t and t in s take O(n^2) time in the worst case using standard string matching
  • The loop runs at most min(m, n) iterations, which is O(n), and each iteration performs string slicing and comparison operations that take O(n) time
  • String concatenation operations take O(n) time

Therefore, the dominant operation is the substring checking with O(n^2) complexity, making the overall time complexity O(n^2), where n is the maximum length of the three strings.

Space Complexity: O(n)

The space usage includes:

  • The ans variable storing a string of length at most 3n in the worst case: O(n)
  • Temporary string s created in each iteration: O(n)
  • String slicing operations create new strings: O(n)
  • The permutations generator uses constant extra space as it generates permutations one at a time

The space complexity is O(n), where n is the maximum length of the three strings.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Overlap Detection Logic

A common mistake is incorrectly implementing the overlap detection between two strings. Developers often make errors in:

  • Using wrong slice indices (e.g., first[:overlap_length] instead of first[-overlap_length:])
  • Not properly handling the concatenation after finding an overlap
  • Checking overlaps in the wrong direction

Example of incorrect implementation:

# WRONG: Checking wrong parts of strings
if first[:overlap_length] == second[:overlap_length]:  # Both from start!
    return first + second[overlap_length:]

# CORRECT: End of first should match beginning of second
if first[-overlap_length:] == second[:overlap_length]:
    return first + second[overlap_length:]

2. Not Considering All Permutations

Some developers might try to optimize prematurely by only checking certain orders or assuming a greedy approach will work. This fails because the optimal order depends on the specific overlaps between strings.

Example scenario where order matters:

  • a = "abc", b = "bcd", c = "cde"
  • Order (a,b,c) gives: "abcde" (length 5)
  • Order (c,b,a) gives: "cdeabcd" (length 7)

Solution: Always check all 6 permutations as the code does.

3. Forgetting the Containment Check

A critical optimization that's easy to miss is checking whether one string already contains another. Without this check, the algorithm might produce unnecessarily long results.

Example of the issue:

# WITHOUT containment check
def merge_two_strings(first, second):
    # Only checking overlaps
    for overlap_length in range(min(len(first), len(second)), 0, -1):
        if first[-overlap_length:] == second[:overlap_length]:
            return first + second[overlap_length:]
    return first + second

# If first="abcdef" and second="cde", this returns "abcdefcde" (length 9)
# But "cde" is already in "abcdef", so we should just return "abcdef" (length 6)

4. Incorrect Lexicographical Comparison

When comparing strings of equal length, developers might forget to check for lexicographical ordering or implement it incorrectly.

Incorrect approach:

# WRONG: Only considering length
if len(merged_string) < len(result):
    result = merged_string

# CORRECT: Also consider lexicographical order for same length
if (result == "" or 
    len(merged_string) < len(result) or 
    (len(merged_string) == len(result) and merged_string < result)):
    result = merged_string

5. Off-by-One Errors in Range

Using incorrect range bounds when checking overlaps is a frequent error:

Wrong:

# Missing potential overlaps by starting from wrong index
for overlap_length in range(min(first_len, second_len) - 1, 0, -1):  # Off by one!

Correct:

# Check all possible overlaps from maximum to minimum
for overlap_length in range(min(first_len, second_len), 0, -1):

Prevention Tips:

  1. Test with edge cases where strings are substrings of each other
  2. Verify overlap logic with simple examples manually
  3. Use debug prints to trace the merging process
  4. Test with strings that have different optimal orderings
  5. Include test cases where lexicographical ordering matters
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

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

Load More