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
orword2
still has characters:- You can take the first character from
word1
, append it tomerge
, and remove it fromword1
- OR you can take the first character from
word2
, append it tomerge
, and remove it fromword2
- You can take the first character from
- 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.
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:
-
Initialize Two Pointers: Set
i = 0
forword1
andj = 0
forword2
to track our current position in each string. Create an empty listans
to build our result. -
Main Merging Loop: While both strings have remaining characters (
i < len(word1) and j < len(word2)
):- Compare the suffixes
word1[i:]
withword2[j:]
- If
word1[i:] > word2[j:]
, take the character fromword1
:- Append
word1[i]
toans
- Increment
i
by 1
- Append
- Otherwise, take the character from
word2
:- Append
word2[j]
toans
- Increment
j
by 1
- Append
- Compare the suffixes
-
Handle Remaining Characters: After the loop, one string might still have characters:
- Append
word1[i:]
toans
(empty string ifi
reached the end) - Append
word2[j:]
toans
(empty string ifj
reached the end)
- Append
-
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 EvaluatorExample 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'
toans
- 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'
toans
- 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'
toans
- 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'
toans
- 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"
toans
- 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, requiringO(n + m)
space - The string slices
word1[i:]
andword2[j:]
created during comparisons, which in Python create new string objects requiringO(n + m)
space in total - The final string created by
"".join(ans)
which requiresO(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.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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
Want a Structured Path to Master System Design Too? Donโt Miss This!