187 lines
8.4 KiB
YAML
187 lines
8.4 KiB
YAML
title: Merge Strings Alternately
|
|
slug: merge-strings-alternately
|
|
difficulty: easy
|
|
leetcode_id: 1768
|
|
leetcode_url: https://leetcode.com/problems/merge-strings-alternately/
|
|
categories:
|
|
- strings
|
|
- two-pointers
|
|
patterns:
|
|
- two-pointers
|
|
|
|
description: |
|
|
You are given two strings `word1` and `word2`. Merge the strings by adding letters in alternating order, starting with `word1`. If a string is longer than the other, append the additional letters onto the end of the merged string.
|
|
|
|
Return *the merged string*.
|
|
|
|
constraints: |
|
|
- `1 <= word1.length, word2.length <= 100`
|
|
- `word1` and `word2` consist of lowercase English letters.
|
|
|
|
examples:
|
|
- input: 'word1 = "abc", word2 = "pqr"'
|
|
output: '"apbqcr"'
|
|
explanation: "The merged string is formed by alternating characters: a, p, b, q, c, r."
|
|
- input: 'word1 = "ab", word2 = "pqrs"'
|
|
output: '"apbqrs"'
|
|
explanation: 'Since word2 is longer, "rs" is appended to the end after alternating through "ab" and "pq".'
|
|
- input: 'word1 = "abcd", word2 = "pq"'
|
|
output: '"apbqcd"'
|
|
explanation: 'Since word1 is longer, "cd" is appended to the end after alternating through "ab" and "pq".'
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you have two decks of cards and you want to shuffle them together by taking one card from each deck alternately. You pick a card from the first deck, then one from the second, then back to the first, and so on.
|
|
|
|
When one deck runs out before the other, you simply place all the remaining cards from the longer deck on top.
|
|
|
|
This problem works exactly the same way with strings. Think of each character as a card. We **interleave** characters from both strings one at a time, and when one string is exhausted, we append whatever remains from the other.
|
|
|
|
The key insight is that we can process both strings simultaneously using either:
|
|
1. **Two pointers** - one for each string, advancing in lockstep
|
|
2. **Index-based iteration** - iterating up to the length of the longer string
|
|
|
|
Since we're building a new string character by character, this is a straightforward linear traversal problem.
|
|
|
|
approach: |
|
|
We solve this using a **Two Pointers** approach:
|
|
|
|
**Step 1: Initialise variables**
|
|
|
|
- `result`: An empty list to collect characters (using a list is more efficient than string concatenation in Python)
|
|
- `i`: Pointer starting at `0` to track position in both strings
|
|
|
|
|
|
|
|
**Step 2: Iterate while either string has characters**
|
|
|
|
- Use a `while` loop that continues as long as `i` is less than either string's length
|
|
- If `i < len(word1)`: append `word1[i]` to result
|
|
- If `i < len(word2)`: append `word2[i]` to result
|
|
- Increment `i` after each iteration
|
|
|
|
|
|
|
|
**Step 3: Return the merged result**
|
|
|
|
- Join the list into a single string and return it
|
|
|
|
|
|
|
|
This approach naturally handles unequal string lengths. When one string is exhausted, only the remaining characters from the other string are appended.
|
|
|
|
common_pitfalls:
|
|
- title: String Concatenation in a Loop
|
|
description: |
|
|
In Python, using `result += char` inside a loop creates a new string object each time because strings are immutable. This leads to **O(n^2)** time complexity for building a string of length n.
|
|
|
|
Instead, collect characters in a list and use `''.join()` at the end for O(n) performance. While this optimisation may seem unnecessary for small strings (up to 200 characters here), it's a good habit for larger inputs.
|
|
wrong_approach: "result = result + word1[i] in a loop"
|
|
correct_approach: "Append to a list, then ''.join() at the end"
|
|
|
|
- title: Off-by-One Errors with Unequal Lengths
|
|
description: |
|
|
When iterating over two strings of different lengths, it's easy to accidentally index out of bounds.
|
|
|
|
For example, if `word1 = "ab"` and `word2 = "pqrs"`, iterating to `max(len(word1), len(word2))` means indices 2 and 3 are valid for `word2` but not for `word1`.
|
|
|
|
Always check `if i < len(word)` before accessing `word[i]`.
|
|
wrong_approach: "Accessing word1[i] without bounds check"
|
|
correct_approach: "Guard each access with if i < len(word)"
|
|
|
|
- title: Forgetting to Handle Remaining Characters
|
|
description: |
|
|
A common mistake is stopping the loop when the shorter string ends, forgetting to append the remaining characters from the longer string.
|
|
|
|
The condition `while i < len(word1) or i < len(word2)` ensures we continue until *both* strings are fully processed.
|
|
wrong_approach: "while i < len(word1) and i < len(word2)"
|
|
correct_approach: "while i < len(word1) or i < len(word2)"
|
|
|
|
key_takeaways:
|
|
- "**Two-pointer pattern**: When processing two sequences in parallel, use indices or pointers to track progress through each"
|
|
- "**Handle unequal lengths gracefully**: Use `or` in loop conditions and bounds checks to avoid index errors"
|
|
- "**String building efficiency**: In Python, prefer list accumulation with `join()` over repeated string concatenation"
|
|
- "**Foundation for harder problems**: This interleaving pattern appears in merge operations (merge sort), zipper problems, and alternating data structures"
|
|
|
|
time_complexity: "O(n + m). We iterate through each character of `word1` (length n) and `word2` (length m) exactly once."
|
|
space_complexity: "O(n + m). We create a new string of length `n + m` to store the result."
|
|
|
|
solutions:
|
|
- approach_name: Two Pointers
|
|
is_optimal: true
|
|
code: |
|
|
def merge_alternately(word1: str, word2: str) -> str:
|
|
# Use a list for efficient character accumulation
|
|
result = []
|
|
i = 0
|
|
|
|
# Continue while either string has characters left
|
|
while i < len(word1) or i < len(word2):
|
|
# Add character from word1 if available
|
|
if i < len(word1):
|
|
result.append(word1[i])
|
|
# Add character from word2 if available
|
|
if i < len(word2):
|
|
result.append(word2[i])
|
|
i += 1
|
|
|
|
# Join list into final string
|
|
return ''.join(result)
|
|
explanation: |
|
|
**Time Complexity:** O(n + m) — We visit each character exactly once.
|
|
|
|
**Space Complexity:** O(n + m) — The result string contains all characters from both inputs.
|
|
|
|
We use a single index `i` to traverse both strings simultaneously. The `or` condition ensures we process all characters even when strings have different lengths. Bounds checking before each access prevents index errors.
|
|
|
|
- approach_name: Itertools Zip Longest
|
|
is_optimal: true
|
|
code: |
|
|
from itertools import zip_longest
|
|
|
|
def merge_alternately(word1: str, word2: str) -> str:
|
|
# zip_longest pairs characters, filling with '' when one string ends
|
|
result = []
|
|
for c1, c2 in zip_longest(word1, word2, fillvalue=''):
|
|
result.append(c1)
|
|
result.append(c2)
|
|
return ''.join(result)
|
|
explanation: |
|
|
**Time Complexity:** O(n + m) — Single pass through both strings.
|
|
|
|
**Space Complexity:** O(n + m) — Result string stores all characters.
|
|
|
|
Python's `zip_longest` pairs elements from both strings, using `''` (empty string) as a fill value when one string is shorter. This elegantly handles unequal lengths without explicit bounds checking.
|
|
|
|
- approach_name: Two Separate Pointers
|
|
is_optimal: false
|
|
code: |
|
|
def merge_alternately(word1: str, word2: str) -> str:
|
|
result = []
|
|
i, j = 0, 0
|
|
|
|
# Alternate while both strings have characters
|
|
while i < len(word1) and j < len(word2):
|
|
result.append(word1[i])
|
|
result.append(word2[j])
|
|
i += 1
|
|
j += 1
|
|
|
|
# Append remaining characters from word1
|
|
while i < len(word1):
|
|
result.append(word1[i])
|
|
i += 1
|
|
|
|
# Append remaining characters from word2
|
|
while j < len(word2):
|
|
result.append(word2[j])
|
|
j += 1
|
|
|
|
return ''.join(result)
|
|
explanation: |
|
|
**Time Complexity:** O(n + m) — Each character visited once across the three loops.
|
|
|
|
**Space Complexity:** O(n + m) — Result string contains all characters.
|
|
|
|
This approach uses separate pointers `i` and `j` for each string. The first loop handles the interleaving, and the two remaining loops handle leftover characters. While slightly more verbose than the single-pointer approach, this pattern is useful when the two sequences need different handling during iteration.
|