Facebook Pixel

1754. Largest Merge Of Two Strings

Problem Description

You are given two strings word1 and word2. Your task is to construct a new string called merge by repeatedly choosing characters from either string following these rules:

The Merging Process:

  • Start with an empty merge string
  • While at least one of word1 or word2 still has characters:
    • You can take the first character from word1, append it to merge, and remove it from word1
    • OR you can take the first character from word2, append it to merge, and remove it from word2
  • Continue until both strings are empty

The Goal: Return the lexicographically largest possible merge string you can create.

What is Lexicographically Larger? A string is lexicographically larger than another if at the first position where they differ, it has a character that comes later in the alphabet. For example, "abcd" is lexicographically larger than "abcc" because at position 3, 'd' > 'c'.

Example Walkthrough: If word1 = "abc" and word2 = "def":

  • You could take from word2 first: merge = "d", word2 = "ef"
  • Then from word2 again: merge = "de", word2 = "f"
  • Continue optimally to get the largest possible result

The key insight is that at each step, you should choose the option that leads to the lexicographically largest final string. This means not just comparing single characters, but considering the entire remaining portions of each string to make the optimal choice.

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

Intuition

The key insight is that when deciding which character to take next, we shouldn't just compare the immediate characters at the current positions. Instead, we need to consider the entire remaining portions of both strings.

Why? Consider this example: word1 = "ba" and word2 = "bb". If we only compare the first characters ('b' vs 'b'), they're equal. But if we look at the entire remaining strings ("ba" vs "bb"), we see that "bb" is lexicographically larger than "ba". Taking from word2 first gives us a better result.

This leads to the greedy strategy: at each step, compare the remaining suffixes word1[i:] and word2[j:]. Choose to take from whichever string has the lexicographically larger suffix. This ensures that we're always making the choice that leads to the largest possible final string.

The beauty of this approach is that Python's string comparison naturally handles the suffix comparison for us. When we write word1[i:] > word2[j:], it compares the strings character by character from left to right, which is exactly what we need.

After the main loop, when one string is exhausted, we simply append whatever remains of the other string to our result, since there's no choice left to make.

This greedy approach works because at each decision point, choosing the lexicographically larger suffix guarantees that we're maximizing the result at that position without compromising future positions - the optimal substructure property that makes greedy algorithms work.

Learn more about Greedy and Two Pointers patterns.

Solution Approach

The implementation uses a two-pointer greedy approach with suffix comparison:

Algorithm Steps:

  1. Initialize Two Pointers: Set i = 0 for word1 and j = 0 for word2 to track our current position in each string. Create an empty list ans to build our result.

  2. Main Merging Loop: While both strings have remaining characters (i < len(word1) and j < len(word2)):

    • Compare the suffixes word1[i:] with word2[j:]
    • If word1[i:] > word2[j:], take the character from word1:
      • Append word1[i] to ans
      • Increment i by 1
    • Otherwise, take the character from word2:
      • Append word2[j] to ans
      • Increment j by 1
  3. Handle Remaining Characters: After the loop, one string might still have characters:

    • Append word1[i:] to ans (empty string if i reached the end)
    • Append word2[j:] to ans (empty string if j reached the end)
  4. Build Final Result: Join all characters in ans using "".join(ans) to create the final merged string.

Why This Works:

The suffix comparison word1[i:] > word2[j:] is the critical operation. Python compares these strings lexicographically from left to right. By always choosing the string with the larger suffix, we ensure that:

  • We get the largest possible character at the current position
  • We maintain the best possible options for future positions

Time Complexity: O(n * (m + n)) where n and m are the lengths of word1 and word2. In the worst case, we perform n + m iterations, and each suffix comparison takes O(m + n) time.

Space Complexity: O(m + n) for storing the result string.

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 word1 = "cab" and word2 = "ba":

Initial state:

  • i = 0, j = 0
  • ans = []
  • Remaining: word1[0:] = "cab", word2[0:] = "ba"

Step 1: Compare suffixes "cab" vs "ba"

  • "cab" > "ba" lexicographically (first character 'c' > 'b')
  • Take from word1: append 'c' to ans
  • Move pointer: i = 1
  • Result so far: ans = ['c']

Step 2: Compare suffixes "ab" vs "ba"

  • "ab" < "ba" lexicographically (second character 'b' < 'a' after both start with same char)
  • Take from word2: append 'b' to ans
  • Move pointer: j = 1
  • Result so far: ans = ['c', 'b']

Step 3: Compare suffixes "ab" vs "a"

  • "ab" > "a" lexicographically (has more characters after matching prefix)
  • Take from word1: append 'a' to ans
  • Move pointer: i = 2
  • Result so far: ans = ['c', 'b', 'a']

Step 4: Compare suffixes "b" vs "a"

  • "b" > "a" lexicographically ('b' > 'a')
  • Take from word1: append 'b' to ans
  • Move pointer: i = 3
  • Result so far: ans = ['c', 'b', 'a', 'b']

Step 5: word1 is exhausted (i = 3), append remaining from word2

  • Append word2[1:] = "a" to ans
  • Final result: ans = ['c', 'b', 'a', 'b', 'a']

Final merged string: "cbaba"

This example demonstrates why suffix comparison is crucial. In Step 2, even though both strings start with the same character at their current positions ('a' and 'b'), comparing the full suffixes "ab" vs "ba" correctly identifies that taking from word2 leads to a better result.

Solution Implementation

1class Solution:
2    def largestMerge(self, word1: str, word2: str) -> str:
3        # Initialize pointers for both strings
4        i = 0  # Pointer for word1
5        j = 0  # Pointer for word2
6      
7        # List to build the result string
8        result = []
9      
10        # Merge the two strings by choosing the lexicographically larger suffix
11        while i < len(word1) and j < len(word2):
12            # Compare the remaining substrings from current positions
13            # Choose from word1 if its suffix is lexicographically larger
14            if word1[i:] > word2[j:]:
15                result.append(word1[i])
16                i += 1
17            else:
18                # Otherwise, choose from word2
19                result.append(word2[j])
20                j += 1
21      
22        # Append any remaining characters from word1
23        result.append(word1[i:])
24      
25        # Append any remaining characters from word2
26        result.append(word2[j:])
27      
28        # Join all parts into the final merged string
29        return "".join(result)
30
1class Solution {
2    /**
3     * Constructs the lexicographically largest merge string from two input strings.
4     * At each step, we choose the character from the string whose remaining suffix
5     * is lexicographically larger.
6     * 
7     * @param word1 First input string
8     * @param word2 Second input string
9     * @return The lexicographically largest possible merge string
10     */
11    public String largestMerge(String word1, String word2) {
12        // Get lengths of both input strings
13        int word1Length = word1.length();
14        int word2Length = word2.length();
15      
16        // Initialize pointers for traversing both strings
17        int pointer1 = 0;
18        int pointer2 = 0;
19      
20        // StringBuilder to efficiently build the result string
21        StringBuilder mergedResult = new StringBuilder();
22      
23        // Process characters while both strings have remaining characters
24        while (pointer1 < word1Length && pointer2 < word2Length) {
25            // Compare the remaining suffixes of both strings
26            // Choose from the string with lexicographically larger suffix
27            boolean isWord1SuffixGreater = word1.substring(pointer1).compareTo(word2.substring(pointer2)) > 0;
28          
29            // Append the character from the chosen string and advance its pointer
30            if (isWord1SuffixGreater) {
31                mergedResult.append(word1.charAt(pointer1));
32                pointer1++;
33            } else {
34                mergedResult.append(word2.charAt(pointer2));
35                pointer2++;
36            }
37        }
38      
39        // Append any remaining characters from word1
40        mergedResult.append(word1.substring(pointer1));
41      
42        // Append any remaining characters from word2
43        mergedResult.append(word2.substring(pointer2));
44      
45        // Convert StringBuilder to String and return
46        return mergedResult.toString();
47    }
48}
49
1class Solution {
2public:
3    string largestMerge(string word1, string word2) {
4        // Get the lengths of both input strings
5        int word1Length = word1.size();
6        int word2Length = word2.size();
7      
8        // Initialize pointers for traversing both strings
9        int pointer1 = 0;
10        int pointer2 = 0;
11      
12        // Result string to store the largest merge
13        string result;
14      
15        // Continue merging while both strings have remaining characters
16        while (pointer1 < word1Length && pointer2 < word2Length) {
17            // Compare the remaining substrings lexicographically
18            // If word1's remaining substring is greater, take from word1
19            // Otherwise, take from word2
20            bool isWord1Greater = word1.substr(pointer1) > word2.substr(pointer2);
21          
22            if (isWord1Greater) {
23                // Append character from word1 and advance its pointer
24                result += word1[pointer1];
25                pointer1++;
26            } else {
27                // Append character from word2 and advance its pointer
28                result += word2[pointer2];
29                pointer2++;
30            }
31        }
32      
33        // Append any remaining characters from word1
34        result += word1.substr(pointer1);
35      
36        // Append any remaining characters from word2
37        result += word2.substr(pointer2);
38      
39        return result;
40    }
41};
42
1/**
2 * Constructs the lexicographically largest merge string from two input strings
3 * by greedily selecting characters while maintaining relative order within each string
4 * 
5 * @param word1 - First input string
6 * @param word2 - Second input string
7 * @returns The lexicographically largest possible merge string
8 */
9function largestMerge(word1: string, word2: string): string {
10    // Get lengths of both input strings
11    const word1Length: number = word1.length;
12    const word2Length: number = word2.length;
13  
14    // Initialize result string to build the merge
15    let mergedResult: string = '';
16  
17    // Initialize pointers for traversing both strings
18    let pointer1: number = 0;
19    let pointer2: number = 0;
20  
21    // Process both strings while characters remain in both
22    while (pointer1 < word1Length && pointer2 < word2Length) {
23        // Compare remaining substrings lexicographically
24        // Choose character from the string with larger remaining suffix
25        if (word1.slice(pointer1) > word2.slice(pointer2)) {
26            mergedResult += word1[pointer1];
27            pointer1++;
28        } else {
29            mergedResult += word2[pointer2];
30            pointer2++;
31        }
32    }
33  
34    // Append any remaining characters from word1
35    mergedResult += word1.slice(pointer1);
36  
37    // Append any remaining characters from word2
38    mergedResult += word2.slice(pointer2);
39  
40    return mergedResult;
41}
42

Time and Space Complexity

Time Complexity: O((n + m) * min(n, m)) where n = len(word1) and m = len(word2).

The algorithm uses a two-pointer approach that iterates through both strings. In the worst case, we traverse all characters from both strings, giving us O(n + m) iterations of the while loop. However, within each iteration, we perform string slicing comparisons word1[i:] and word2[j:].

String comparison in Python takes O(min(len(word1[i:]), len(word2[j:]))) time in the worst case. In the beginning, these slices can be as long as O(n) and O(m) respectively. On average, the comparison cost is O(min(n - i, m - j)).

Since we perform up to n + m comparisons and each comparison can take up to O(min(n, m)) time in the worst case, the overall time complexity is O((n + m) * min(n, m)).

Space Complexity: O(n + m)

The space complexity consists of:

  • The ans list which stores all characters from both input strings, requiring O(n + m) space
  • The string slices word1[i:] and word2[j:] created during comparisons, which in Python create new string objects requiring O(n + m) space in total
  • The final string created by "".join(ans) which requires O(n + m) space

Therefore, the total space complexity is O(n + m).

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

Common Pitfalls

Pitfall 1: Greedy Character-by-Character Comparison

The Mistake: A common initial approach is to simply compare the current characters at positions i and j:

# INCORRECT approach
if word1[i] > word2[j]:
    result.append(word1[i])
    i += 1
elif word2[j] > word1[i]:
    result.append(word2[j])
    j += 1
else:
    # When characters are equal, just pick one arbitrarily
    result.append(word1[i])
    i += 1

Why It Fails: This fails when the current characters are equal. Consider word1 = "ca" and word2 = "cb":

  • Both start with 'c'
  • The incorrect approach might arbitrarily choose from word1, giving us "cca" + "b" = "ccab"
  • But the optimal choice is to take from word2 first, giving us "cbc" + "a" = "cbca"
  • "cbca" > "ccab" lexicographically

The Solution: Always compare the entire remaining suffixes word1[i:] vs word2[j:], not just single characters. This ensures we consider the full context when making decisions.

Pitfall 2: Inefficient String Concatenation

The Mistake: Building the result string using repeated string concatenation:

# INEFFICIENT approach
result = ""
while i < len(word1) and j < len(word2):
    if word1[i:] > word2[j:]:
        result += word1[i]  # String concatenation creates new string each time
        i += 1
    else:
        result += word2[j]
        j += 1

Why It's Problematic: In Python, strings are immutable. Each += operation creates a new string object, copying all previous characters. This leads to O((m+n)ยฒ) time complexity for string building alone.

The Solution: Use a list to collect characters and join them once at the end:

result = []
# ... append characters to list
return "".join(result)  # Single O(m+n) operation

Pitfall 3: Incorrect Handling of Remaining Characters

The Mistake: Using a loop to append remaining characters one by one:

# After main loop
while i < len(word1):
    result.append(word1[i])
    i += 1
while j < len(word2):
    result.append(word2[j])
    j += 1

Why It's Suboptimal: While this works correctly, it's unnecessarily verbose and less efficient than using string slicing.

The Solution: Use string slicing to append all remaining characters at once:

result.append(word1[i:])  # Appends remaining portion or empty string
result.append(word2[j:])  # Appends remaining portion or empty string

This is cleaner and more efficient since slicing operations are optimized in Python.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More