Rearrange Spaces Between Words
EasyPrompt
You are given a string text of words that are placed among some number of spaces. Each word consists of one or more lowercase English letters and are separated by at least one space. It's guaranteed that text contains at least one word.
Rearrange the spaces so that there is an equal number of spaces between every pair of adjacent words and that number is maximized. If you cannot redistribute all the spaces equally, place the extra spaces at the end, meaning the returned string should be the same length as text.
Return the string after rearranging the spaces.
Example 1:
Input: text = " this is a sentence "
Output: "this is a sentence"
Explanation: There are a total of 9 spaces and 4 words. We can evenly divide the 9 spaces between the words: 9 / (4-1) = 3 spaces.Example 2:
Input: text = " practice makes perfect"
Output: "practice makes perfect "
Explanation: There are a total of 7 spaces and 3 words. 7 / (3-1) = 3 spaces plus 1 extra space. We place this extra space at the end of the string.
Constraints:
1 <= text.length <= 100textconsists of lowercase English letters and' '.textcontains at least one word.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach first extracts all words and counts the total number of spaces. Then, it calculates how many spaces should go between words and how many are left over. Finally, it reconstructs the string by repeatedly concatenating words and separators in a loop using the + operator. This method is straightforward to understand but highly inefficient.
Algorithm
- Count the total number of spaces in
text. - Get an array of words by trimming
textand splitting by one or more whitespace characters (e.g., usingtext.trim().split("\\s+")). - Handle the special case where there is only one word. In this case, the result is the word followed by all the spaces.
- Calculate the number of spaces to put in each gap:
spacesPerGap = totalSpaces / (wordCount - 1). - Calculate the number of extra spaces to put at the end:
extraSpaces = totalSpaces % (wordCount - 1). - Create a separator string consisting of
spacesPerGapspaces. - Initialize an empty string, then loop through the words, appending each word and the separator using the
+operator for string concatenation. - After the loop, append the extra spaces to the end.
- Return the final string.
Walkthrough
The core of this method lies in its two main phases: analysis and reconstruction.
-
Analysis: The input string
textis first processed to gather necessary information. We count the total number of space characters. Simultaneously, we extract the words. A simple way to do this is by using built-in functions liketrim()to remove leading/trailing whitespace andsplit("\\s+")to get an array of words. -
Reconstruction: After calculating the ideal number of spaces for each gap and the leftover spaces, we build the new string. This is where the inefficiency is introduced. A
forloop iterates through the array of words, and inside the loop, the+operator is used to append the current word and the separator string to a result string. In Java, strings are immutable. Each timeresult = result + wordis executed, a new string object is created in memory, and the contents of the oldresultandwordare copied into it. This process is repeated for every word, leading to quadratic time complexity.
class Solution { public String reorderSpaces(String text) { int spaceCount = 0; for (char c : text.toCharArray()) { if (c == ' ') { spaceCount++; } } String[] words = text.trim().split("\\s+"); int wordCount = words.length; if (wordCount == 1) { return words[0] + " ".repeat(spaceCount); } int spacesPerGap = spaceCount / (wordCount - 1); int extraSpaces = spaceCount % (wordCount - 1); String separator = " ".repeat(spacesPerGap); String trailingSpaces = " ".repeat(extraSpaces); String result = ""; for (int i = 0; i < wordCount; i++) { result += words[i]; if (i < wordCount - 1) { result += separator; } } result += trailingSpaces; return result; }}Complexity
Time
O(N^2), where N is the length of the input string. While counting spaces and splitting the string is O(N), the string concatenation in a loop dominates. If there are K words, building the string takes roughly O(K * N) time, which in the worst case is O(N^2).
Space
O(N^2), where N is the length of the input string. The `split` operation takes O(N) space, but the primary issue is the loop for concatenation, which creates numerous intermediate string objects. The total memory consumed by these objects can be on the order of O(N^2).
Trade-offs
Pros
Conceptually simple and easy to write for beginners.
Cons
Extremely inefficient for time and space, especially with a large number of words, due to the nature of string immutability in Java.
Can lead to
OutOfMemoryErrorfor large inputs, although not an issue with the given constraints.
This approach significantly improves upon the naive method by using a StringBuilder for string construction. The initial steps of counting spaces and extracting words remain the same. However, instead of using the + operator in a loop, it appends words and spaces to a mutable StringBuilder object. This avoids the creation of countless intermediate string objects, making the string construction process linear in time.
Algorithm
- Count the total number of spaces by iterating through the string.
- Extract the words, for instance, by using
text.trim().split("\\s+"). - Handle the single-word edge case separately.
- Calculate
spacesPerGapandextraSpacesas in the previous approach. - Initialize a
StringBuilder. - Iterate through the list of words, appending each word to the
StringBuilder. If it's not the last word, appendspacesPerGapspaces. - After the loop, append the
extraSpacesto theStringBuilder. - Convert the
StringBuilderto a string and return it.
Walkthrough
This method is a standard and efficient way to solve string manipulation problems in Java. It correctly identifies the performance bottleneck of the naive approach—string concatenation—and replaces it with a proper tool.
-
Analysis: This phase is identical to the naive approach. We get the
spaceCountand a list ofwords. -
Reconstruction with StringBuilder: A
StringBuilderis initialized. We loop through our list of words. In each iteration, we append the word and, if it's not the last one, the calculated number of separator spaces. BecauseStringBuilderuses a mutable internal character array, these append operations are very fast (amortized constant time, relative to the size of the appended content). After the loop finishes, we append any extra spaces. Finally,sb.toString()is called once to create the final, immutableStringobject.
An even more concise implementation can use String.join(), which is highly optimized and uses a StringBuilder or a similar efficient mechanism internally.
import java.util.Arrays;import java.util.List;import java.util.stream.Collectors; class Solution { public String reorderSpaces(String text) { int spaceCount = (int) text.chars().filter(c -> c == ' ').count(); List<String> words = Arrays.stream(text.trim().split("\\s+")) .filter(s -> !s.isEmpty()) .collect(Collectors.toList()); int wordCount = words.size(); if (wordCount == 1) { return words.get(0) + " ".repeat(spaceCount); } int spacesPerGap = spaceCount / (wordCount - 1); int extraSpaces = spaceCount % (wordCount - 1); String separator = " ".repeat(spacesPerGap); String trailingSpaces = " ".repeat(extraSpaces); return String.join(separator, words) + trailingSpaces; }}Complexity
Time
O(N). Counting spaces, splitting the string, and building the new string with `StringBuilder` or `String.join` are all linear time operations with respect to the input string length N.
Space
O(N). Space is required to store the list of words (O(N) in the worst case) and for the `StringBuilder`'s internal buffer (which will be of size N for the final string).
Trade-offs
Pros
Efficient with O(N) time and space complexity.
Readable and concise, especially when using
String.join().It's the standard idiomatic way to solve such problems in Java.
Cons
Relies on built-in functions like
trimandsplit, which may have slightly more overhead than a purely manual implementation.
This approach is the most optimized in terms of raw performance, although it shares the same asymptotic complexity as the previous one. It avoids the overhead associated with regular expressions in split() and intermediate string creation from trim() by performing a manual, character-by-character parse of the string. It consists of two passes: a manual parsing pass to collect words and count spaces, and a construction pass using a StringBuilder.
Algorithm
- Initialize an empty
List<String>for words and an integerspaceCountto 0. - Iterate through the input string
textwith an indexi. - If the character at
iis a space, incrementspaceCountand advancei. - If the character is a letter, it marks the start of a word. Use a second pointer
jto find the end of the word (the next space or end of string). - Extract the word using
text.substring(i, j)and add it to the list. - Update
itojto continue scanning from where the word ended. - After this first pass, you have the list of words and the total space count.
- The second pass is for construction: calculate space distribution and use a
StringBuilder(orString.join) to build the final string, same as in the previous approach.
Walkthrough
This method provides the most control over the process by handling parsing at a low level.
-
Manual Parsing Pass: We iterate through the string with a single pointer
i. We check if the current character is a space or a letter. If it's a space, we simply count it and move on. If it's a letter, we know we've found a word. We then use another pointer,j, to scan forward fromiuntil we hit a space or the end of the string. The substring fromitojis our word, which we add to a list. We then update our main pointeritojto avoid re-scanning the word. This loop continues until the entire string is processed. -
Construction Pass: This part is identical to the previous efficient approach. With the list of words and the total space count, we can calculate the distribution and use
StringBuilderorString.jointo assemble the final string efficiently.
This manual approach can be slightly faster in practice because it avoids the overhead of regex compilation/matching and the creation of a temporary trimmed string.
import java.util.ArrayList;import java.util.List; class Solution { public String reorderSpaces(String text) { List<String> words = new ArrayList<>(); int spaceCount = 0; int n = text.length(); int i = 0; while (i < n) { if (text.charAt(i) == ' ') { spaceCount++; i++; } else { int j = i; while (j < n && text.charAt(j) != ' ') { j++; } words.add(text.substring(i, j)); i = j; } } int wordCount = words.size(); if (wordCount == 1) { return words.get(0) + " ".repeat(spaceCount); } int spacesPerGap = spaceCount / (wordCount - 1); int extraSpaces = spaceCount % (wordCount - 1); StringBuilder sb = new StringBuilder(); sb.append(String.join(" ".repeat(spacesPerGap), words)); sb.append(" ".repeat(extraSpaces)); return sb.toString(); }}Complexity
Time
O(N). The manual parse is a single linear scan, and the string construction is also linear. The overall complexity remains O(N).
Space
O(N). Space is required for the `words` list and the `StringBuilder` used for construction, both of which are proportional to the input size N.
Trade-offs
Pros
Potentially the fastest O(N) solution in practice due to lower constant factors.
Avoids overhead from regex and intermediate string objects during the parsing phase.
Cons
More verbose and complex to implement correctly compared to using high-level built-in functions.
Prone to off-by-one errors in pointer manipulation.
Solutions
Solution
class Solution {public String reorderSpaces(String text) { int cnt = 0; for (char c : text.toCharArray()) { if (c == ' ') { ++cnt; } } String[] words = text.split("\\s+"); List<String> res = new ArrayList<>(); for (String w : words) { if (!"".equals(w)) { res.add(w); } } int m = res.size() - 1; if (m == 0) { return res.get(0) + " ".repeat(cnt); } String ans = String.join(" ".repeat(cnt / m), res); ans += " ".repeat(cnt % m); return ans; }}class Solution: def reorderSpaces(self, text: str) -> str: cnt = text . count(' ') words = text . split() m = len(words) - 1 if m == 0: return words[0] + ' ' * cnt return (' ' * (cnt // m)). join(words) + ' ' * (cnt % m)class Solution {public: string reorderSpaces(string text) { int spaces = ranges ::count(text, ' '); auto words = split(text); if (words.size() == 1) { return words[0] + string(spaces, ' '); } int cnt = spaces / (words.size() - 1); int mod = spaces % (words.size() - 1); string result = join(words, string(cnt, ' ')); result += string(mod, ' '); return result; } private: vector<string> split(const string &text) { vector<string> words; istringstream stream(text); string word; while (stream >> word) { words.push_back(word); } return words; } string join(const vector<string> &words, const string &separator) { ostringstream result; for (size_t i = 0; i < words.size(); ++i) { result << words[i]; if (i < words.size() - 1) { result << separator; } } return result.str(); }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.