LeetCode 2788 - Split Strings by Separator
This problem asks us to process an array of strings and split each string using a given separator character. After performing all splits, we must collect every resulting substring into a single output array while preserving the original order.
Difficulty: 🟢 Easy
Topics: Array, String
Solution
LeetCode 2788 - Split Strings by Separator
Problem Understanding
This problem asks us to process an array of strings and split each string using a given separator character. After performing all splits, we must collect every resulting substring into a single output array while preserving the original order.
More specifically, we are given:
words, an array of strings.separator, a single character.
For each string in words, we split it wherever the separator appears. The separator itself is not included in the resulting pieces. After splitting, we add all non-empty pieces to the final answer.
The important detail is that empty strings must be excluded. This happens when separators appear at the beginning, end, or consecutively within a string.
For example:
"one.two.three"split by"."becomes["one", "two", "three"]."$easy$"split by"$"becomes["", "easy", ""], but after removing empty strings we keep only["easy"].
The output is a flat array containing all valid substrings from all input words, in the same relative order they are encountered.
The constraints are very small:
- At most 100 words.
- Each word has length at most 20.
This means performance is not a major concern. Even straightforward string processing will easily fit within the limits.
Several edge cases are important:
- A string may start with the separator.
- A string may end with the separator.
- Multiple separators may appear consecutively.
- A string may contain no separator at all.
- A string may consist entirely of separator characters.
- Splitting can generate empty strings that must be removed.
Because the input size is small and the task is fundamentally string processing, a direct solution using the language's built-in split functionality is sufficient.
Approaches
Brute Force Approach
A brute force solution would manually scan every character of every string. We would build the current substring character by character. Whenever we encounter the separator, we would finish the current substring and add it to the answer if it is non-empty. Then we would start building the next substring.
After reaching the end of the string, we would also process the final accumulated substring.
This approach is correct because it explicitly reconstructs exactly the segments between separator occurrences. However, it requires implementing splitting logic manually, which is unnecessary because modern languages already provide optimized string splitting functions.
Optimal Approach
The key observation is that the problem is exactly what string splitting functions are designed for.
For each word:
- Split the string using the separator.
- Iterate through the resulting pieces.
- Add only non-empty pieces to the answer.
This directly mirrors the problem statement. Since built-in split operations already handle all separator positions correctly, including leading, trailing, and consecutive separators, the implementation becomes both simple and reliable.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(N) | O(N) | Manually scans every character and constructs substrings |
| Optimal | O(N) | O(N) | Uses built-in split functionality and filters empty strings |
Here, N represents the total number of characters across all strings.
Algorithm Walkthrough
- Create an empty result array that will store all valid substrings.
- Iterate through every string in
words. - For the current word, split it using the separator character.
- The split operation returns multiple pieces. Some of these pieces may be empty strings if separators appear consecutively or at the boundaries.
- Iterate through every piece produced by the split.
- If the piece is non-empty, append it to the result array.
- Ignore empty strings because the problem explicitly requires excluding them.
- After all words have been processed, return the result array.
Why it works
The split operation divides each string into exactly the substrings between separator occurrences. Every valid substring appears in the returned pieces, and every empty piece originates from adjacent separators or separators at string boundaries. By filtering out empty strings and preserving iteration order, the algorithm produces exactly the output required by the problem.
Python Solution
from typing import List
class Solution:
def splitWordsBySeparator(self, words: List[str], separator: str) -> List[str]:
result = []
for word in words:
parts = word.split(separator)
for part in parts:
if part:
result.append(part)
return result
The implementation starts by creating an empty list named result.
For every word in the input array, we call Python's split() method using the provided separator. This produces all pieces between separator occurrences.
We then iterate through those pieces. Whenever a piece is non-empty, it is appended to the answer. Empty strings are skipped automatically through the if part: condition.
Finally, after all words have been processed, the completed result list is returned.
Go Solution
import "strings"
func splitWordsBySeparator(words []string, separator byte) []string {
var result []string
sep := string(separator)
for _, word := range words {
parts := strings.Split(word, sep)
for _, part := range parts {
if part != "" {
result = append(result, part)
}
}
}
return result
}
The Go solution follows the same logic as the Python version.
Since strings.Split expects a string separator, we first convert the input byte separator into a string using string(separator).
The result is stored in a slice. As we iterate through the split pieces, only non-empty strings are appended. Go slices grow dynamically, making them a natural choice for this problem.
Worked Examples
Example 1
Input
words = ["one.two.three", "four.five", "six"]
separator = "."
Processing
| Word | Split Result | Added to Answer |
|---|---|---|
| "one.two.three" | ["one", "two", "three"] | one, two, three |
| "four.five" | ["four", "five"] | four, five |
| "six" | ["six"] | six |
Result Evolution
| Step | Result |
|---|---|
| Start | [] |
| Add "one" | ["one"] |
| Add "two" | ["one", "two"] |
| Add "three" | ["one", "two", "three"] |
| Add "four" | ["one", "two", "three", "four"] |
| Add "five" | ["one", "two", "three", "four", "five"] |
| Add "six" | ["one", "two", "three", "four", "five", "six"] |
Output
["one","two","three","four","five","six"]
Example 2
Input
words = ["$easy$", "$problem$"]
separator = "$"
Processing
| Word | Split Result | Non-Empty Pieces |
|---|---|---|
| "$easy$" | ["", "easy", ""] | ["easy"] |
| "$problem$" | ["", "problem", ""] | ["problem"] |
Result Evolution
| Step | Result |
|---|---|
| Start | [] |
| Add "easy" | ["easy"] |
| Add "problem" | ["easy", "problem"] |
Output
["easy","problem"]
Example 3
Input
words = ["|||"]
separator = "|"
Processing
| Word | Split Result | Non-Empty Pieces |
|---|---|---|
| " |
Result Evolution
| Step | Result |
|---|---|
| Start | [] |
| No non-empty pieces | [] |
Output
[]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N) | Every character is processed a constant number of times during splitting and filtering |
| Space | O(N) | The output and intermediate split results may collectively store all characters |
Let N be the total number of characters across all strings in words.
Each character participates in at most one split operation and belongs to exactly one resulting substring. Therefore the total work performed is proportional to the input size. The output itself can contain up to all input characters, so linear auxiliary space is required.
Test Cases
from typing import List
s = Solution()
assert s.splitWordsBySeparator(
["one.two.three", "four.five", "six"], "."
) == ["one", "two", "three", "four", "five", "six"] # Example 1
assert s.splitWordsBySeparator(
["$easy$", "$problem$"], "$"
) == ["easy", "problem"] # Example 2
assert s.splitWordsBySeparator(
["|||"], "|"
) == [] # Example 3
assert s.splitWordsBySeparator(
["hello"], "."
) == ["hello"] # No separator present
assert s.splitWordsBySeparator(
[".hello"], "."
) == ["hello"] # Leading separator
assert s.splitWordsBySeparator(
["hello."], "."
) == ["hello"] # Trailing separator
assert s.splitWordsBySeparator(
["a..b"], "."
) == ["a", "b"] # Consecutive separators
assert s.splitWordsBySeparator(
["..."], "."
) == [] # Only separators
assert s.splitWordsBySeparator(
["a.b", "c.d.e"], "."
) == ["a", "b", "c", "d", "e"] # Multiple words
assert s.splitWordsBySeparator(
["a", "b", "c"], "|"
) == ["a", "b", "c"] # No splits required
assert s.splitWordsBySeparator(
["#a##b#"], "#"
) == ["a", "b"] # Mixed leading, trailing, and consecutive separators
Test Summary
| Test | Why |
|---|---|
| Example 1 | Standard splitting across multiple words |
| Example 2 | Leading and trailing separators create empty strings |
| Example 3 | String contains only separators |
["hello"] |
No separator exists in the word |
[".hello"] |
Leading separator handling |
["hello."] |
Trailing separator handling |
["a..b"] |
Consecutive separators produce empty strings |
["..."] |
All resulting pieces are empty |
["a.b", "c.d.e"] |
Multiple input strings |
["a", "b", "c"] |
No splitting needed anywhere |
["#a##b#"] |
Combination of several edge conditions |
Edge Cases
Strings With No Separator
A word may not contain the separator at all. For example, "hello" split by "." produces ["hello"]. A buggy implementation might accidentally discard such words. The current solution correctly keeps the entire string because the split operation returns a single non-empty piece.
Consecutive Separators
Consider "a..b" with separator ".". Splitting produces ["a", "", "b"]. The empty string in the middle does not represent a valid substring and must be excluded. The implementation handles this by only appending pieces that are non-empty.
Leading or Trailing Separators
Inputs such as ".hello" or "hello." generate empty strings at the beginning or end of the split result. Without filtering, these empty strings would incorrectly appear in the output. The condition checking whether a piece is non-empty ensures they are skipped.
Strings Consisting Entirely of Separators
A string like "|||" split by "|" produces only empty strings. The correct answer is an empty array. Since every generated piece is empty, none are appended to the result, producing the required output.