Files
codetutor/backend/data/questions/adding-spaces-to-a-string.yaml

184 lines
9.5 KiB
YAML

title: Adding Spaces to a String
slug: adding-spaces-to-a-string
difficulty: medium
leetcode_id: 2109
leetcode_url: https://leetcode.com/problems/adding-spaces-to-a-string/
categories:
- strings
- arrays
- two-pointers
patterns:
- two-pointers
description: |
You are given a **0-indexed** string `s` and a **0-indexed** integer array `spaces` that describes the indices in the original string where spaces will be added. Each space should be inserted **before** the character at the given index.
For example, given `s = "EnjoyYourCoffee"` and `spaces = [5, 9]`, we place spaces before `'Y'` and `'C'`, which are at indices `5` and `9` respectively. Thus, we obtain `"Enjoy Your Coffee"`.
Return *the modified string **after** the spaces have been added*.
constraints: |
- `1 <= s.length <= 3 * 10^5`
- `s` consists only of lowercase and uppercase English letters.
- `1 <= spaces.length <= 3 * 10^5`
- `0 <= spaces[i] <= s.length - 1`
- All the values of `spaces` are **strictly increasing**.
examples:
- input: 's = "LeetcodeHelpsMeLearn", spaces = [8,13,15]'
output: '"Leetcode Helps Me Learn"'
explanation: "The indices 8, 13, and 15 correspond to the characters 'H', 'M', and 'L' in the original string. We place spaces before those characters."
- input: 's = "icodeinpython", spaces = [1,5,7,9]'
output: '"i code in py thon"'
explanation: "The indices 1, 5, 7, and 9 correspond to the characters 'c', 'i', 'p', and 't'. We place spaces before those characters."
- input: 's = "spacing", spaces = [0,1,2,3,4,5,6]'
output: '" s p a c i n g"'
explanation: "We can also place spaces before the first character of the string, resulting in a leading space."
explanation:
intuition: |
Imagine you're a typist going through a document character by character, and you have a list of positions where you need to insert spaces. Rather than repeatedly inserting spaces into the middle of the string (which would require shifting all subsequent characters), you build a new string from scratch.
The key insight is that the `spaces` array is **sorted in increasing order**. This means as you traverse the original string from left to right, you can simultaneously traverse the `spaces` array from left to right. When your current position in the string matches the next space position, you add a space to your result before adding the character.
Think of it like reading a book while holding a bookmark list. As you read each word (character), you glance at your list to see if this is a spot where you need to pause (insert a space). Since both your reading and your list progress in the same direction, you never need to go backwards.
This **two-pointer technique** allows you to process both the string and the spaces array in a single pass, achieving optimal efficiency.
approach: |
We solve this using a **Two Pointers** approach:
**Step 1: Initialise variables**
- `result`: A list to collect characters (using a list is more efficient than string concatenation in Python)
- `space_idx`: A pointer starting at `0` to track our position in the `spaces` array
&nbsp;
**Step 2: Iterate through each character in the string**
- For each index `i` and character `char` in the string:
- Check if `space_idx` is within bounds AND `spaces[space_idx] == i`
- If true, append a space `' '` to `result` and increment `space_idx`
- Append the current character `char` to `result`
&nbsp;
**Step 3: Build and return the final string**
- Join all characters in the `result` list into a single string
- Return the joined string
&nbsp;
This approach works because the `spaces` array is strictly increasing, guaranteeing that each space position is visited exactly once as we traverse the string left to right.
common_pitfalls:
- title: String Concatenation in a Loop
description: |
A naive approach might use string concatenation (`result += ' '`) inside the loop. In Python, strings are immutable, so each concatenation creates a new string object and copies all previous characters.
With `n` characters and `m` spaces, this leads to **O((n+m)^2)** time complexity in the worst case. For the given constraints (`n, m <= 3 * 10^5`), this will cause **Time Limit Exceeded (TLE)**.
Instead, append characters to a list and use `''.join()` at the end, which is O(n+m).
wrong_approach: "String concatenation with += in a loop"
correct_approach: "Append to a list, then join at the end"
- title: Inserting Spaces In-Place
description: |
Another tempting approach is to insert spaces directly into the string at each position. However, inserting into a string (or even a list) at arbitrary positions requires shifting all subsequent elements.
With `m` insertions into a string of length `n`, this results in **O(n * m)** time complexity, which is too slow for the given constraints.
Building a new string by iterating once avoids this overhead entirely.
wrong_approach: "Using str.insert() or list.insert() for each space"
correct_approach: "Build the result by appending characters sequentially"
- title: Off-by-One Errors with Indices
description: |
The problem states spaces are inserted **before** the character at each index. If you insert **after** the character or misinterpret 0-indexing, your output will be incorrect.
For example, with `s = "abc"` and `spaces = [1]`, the correct output is `"a bc"` (space before index 1, which is 'b'), not `"ab c"`.
Always verify your logic with the examples, especially edge cases like `spaces = [0]` which inserts a space at the very beginning.
key_takeaways:
- "**Two pointers with sorted data**: When both inputs are sorted or one is naturally ordered (like string indices), consider traversing them together with two pointers."
- "**Avoid string concatenation in loops**: In languages with immutable strings (Python, Java, JavaScript), use a list/array/StringBuilder and join at the end for O(n) performance."
- "**Build vs. modify**: When inserting multiple elements into a sequence, building a new sequence is often more efficient than modifying in place."
- "**Leverage problem constraints**: The guarantee that `spaces` is strictly increasing is what makes the single-pass solution possible."
time_complexity: "O(n + m). We traverse the string of length `n` once, and we traverse the `spaces` array of length `m` once."
space_complexity: "O(n + m). We build a result list containing all `n` original characters plus `m` spaces."
solutions:
- approach_name: Two Pointers
is_optimal: true
code: |
def addSpaces(s: str, spaces: list[int]) -> str:
result = []
space_idx = 0
for i, char in enumerate(s):
# Check if we need to insert a space before this character
if space_idx < len(spaces) and spaces[space_idx] == i:
result.append(' ')
space_idx += 1
# Add the current character
result.append(char)
# Join all parts into the final string
return ''.join(result)
explanation: |
**Time Complexity:** O(n + m) — We iterate through the string once (n characters) and through the spaces array once (m elements).
**Space Complexity:** O(n + m) — The result list contains n characters plus m spaces.
We use two pointers: one implicit (the loop variable `i`) for the string, and one explicit (`space_idx`) for the spaces array. Since both traverse their respective sequences in order, we achieve linear time complexity.
- approach_name: Pre-calculate and Join
is_optimal: true
code: |
def addSpaces(s: str, spaces: list[int]) -> str:
# Convert spaces to a set for O(1) lookup
space_set = set(spaces)
result = []
for i, char in enumerate(s):
if i in space_set:
result.append(' ')
result.append(char)
return ''.join(result)
explanation: |
**Time Complexity:** O(n + m) — Building the set is O(m), and iterating through the string with O(1) lookups is O(n).
**Space Complexity:** O(n + m) — The set uses O(m) space, and the result list uses O(n + m) space.
This alternative uses a hash set for O(1) lookups instead of maintaining a pointer. While asymptotically the same, the two-pointer approach is slightly more efficient in practice due to lower constant factors (no hashing overhead). However, this approach may be easier to understand for some.
- approach_name: String Slicing
is_optimal: false
code: |
def addSpaces(s: str, spaces: list[int]) -> str:
parts = []
prev = 0
for space_pos in spaces:
# Add the substring from previous position to current space position
parts.append(s[prev:space_pos])
prev = space_pos
# Add the remaining part of the string
parts.append(s[prev:])
# Join all parts with spaces
return ' '.join(parts)
explanation: |
**Time Complexity:** O(n + m) — Slicing operations are O(k) where k is the slice length, and the total slice lengths sum to n.
**Space Complexity:** O(n + m) — Storing all the substrings plus the joined result.
This approach treats the problem as splitting the string at space positions and rejoining with spaces. While elegant, it creates multiple substring objects which may have slightly higher memory overhead than the character-by-character approach.