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.

LeetCode Problem 2788

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:

  1. Split the string using the separator.
  2. Iterate through the resulting pieces.
  3. 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

  1. Create an empty result array that will store all valid substrings.
  2. Iterate through every string in words.
  3. For the current word, split it using the separator character.
  4. The split operation returns multiple pieces. Some of these pieces may be empty strings if separators appear consecutively or at the boundaries.
  5. Iterate through every piece produced by the split.
  6. If the piece is non-empty, append it to the result array.
  7. Ignore empty strings because the problem explicitly requires excluding them.
  8. 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.