questions M-R
This commit is contained in:
186
backend/data/questions/merge-strings-alternately.yaml
Normal file
186
backend/data/questions/merge-strings-alternately.yaml
Normal file
@@ -0,0 +1,186 @@
|
||||
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.
|
||||
Reference in New Issue
Block a user