186. Reverse Words in a String II π
Problem Description
You are given a character array s
that contains words separated by single spaces. Your task is to reverse the order of the words in the array while keeping each word's internal character order intact.
A word is defined as a sequence of non-space characters. Words in the array are separated by exactly one space character.
The key constraint is that you must solve this problem in-place, meaning you cannot use any extra space for another array or data structure. You must modify the given array directly.
For example, if the input array represents "the sky is blue", after reversing the words, it should become "blue is sky the".
The solution uses a two-step approach:
- First, reverse each individual word in the array. This is done by identifying word boundaries (spaces or array ends) and reversing the characters within each word.
- Then, reverse the entire array. This final reversal puts the words in the opposite order while maintaining the correct character order within each word (since they were pre-reversed in step 1).
The algorithm uses two pointers i
and j
where i
marks the start of a word and j
scans through the array. When a space is encountered at position j
, the word from position i
to j-1
is reversed. After processing all words individually, the entire array is reversed from position 0
to n-1
to achieve the final result.
Intuition
When we need to reverse the order of words in-place, we face a challenge: we can't simply pick up words and move them around because that would require extra space. We need a clever trick.
Think about what happens when you reverse an entire string - everything gets flipped, including the individual words themselves. For example, if we have "the sky is blue" and reverse it entirely, we get "eulb si yks eht". Notice that the words are now in the correct order (blue, is, sky, the), but each word is backwards!
This observation leads us to a key insight: if we first reverse each word individually, and then reverse the entire array, we'll get the desired result.
Let's trace through this logic:
- Original:
"the sky is blue"
- After reversing each word:
"eht yks si eulb"
- After reversing the entire array:
"blue is sky the"
Why does this work? When we reverse the entire array in step 3, two things happen:
- The order of words gets reversed (which is what we want)
- Each word gets reversed again (which cancels out the first reversal from step 2)
It's like a double negative in mathematics - reversing a reversal brings us back to the original orientation. By pre-reversing each word, we ensure that when the final reversal happens, the words end up in their correct character order while their positions in the array are swapped.
This approach is elegant because it only uses the swap operation on the existing array elements, requiring no extra space beyond a few variables to track positions, thus satisfying the in-place requirement.
Solution Approach
The implementation uses a two-pointer technique to achieve the in-place reversal. Here's how the algorithm works step by step:
Helper Function - reverse(i, j)
:
This function reverses a portion of the array from index i
to index j
. It uses the classic two-pointer swap approach:
- Start with pointers at positions
i
andj
- Swap elements at these positions:
s[i], s[j] = s[j], s[i]
- Move pointers toward each other:
i
increases,j
decreases - Continue until pointers meet or cross
Main Algorithm:
-
Initialize variables: Set
i = 0
to track the start of each word, andn = len(s)
for the array length. -
First pass - Reverse individual words:
- Iterate through the array using index
j
and characterc
- When a space is encountered (
c == " "
):- Reverse the current word from position
i
toj-1
- Update
i = j + 1
to point to the start of the next word
- Reverse the current word from position
- When reaching the last character (
j == n - 1
):- Reverse the last word from position
i
toj
- This handles the case where the array doesn't end with a space
- Reverse the last word from position
- Iterate through the array using index
-
Second pass - Reverse entire array:
- Call
reverse(0, n - 1)
to reverse the complete array - This puts the words in the opposite order while restoring each word's correct character sequence
- Call
Example Walkthrough:
For array ['t','h','e',' ','s','k','y']
:
- After reversing individual words:
['e','h','t',' ','y','k','s']
- After reversing entire array:
['s','k','y',' ','t','h','e']
The time complexity is O(n)
where n
is the length of the array, as we traverse the array twice. The space complexity is O(1)
since we only use a constant amount of extra variables, meeting the in-place requirement.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with the input array: ['c','a','t',' ','i','s']
Initial State:
['c','a','t',' ','i','s'] 0 1 2 3 4 5
Step 1: Reverse individual words
Set i = 0
(start of first word), scan with j
:
j = 0
: 'c' is not a space, continuej = 1
: 'a' is not a space, continuej = 2
: 't' is not a space, continuej = 3
: ' ' is a space! Reverse word fromi=0
toj-1=2
- Reverse ['c','a','t'] β ['t','a','c']
- Array becomes:
['t','a','c',' ','i','s']
- Set
i = j + 1 = 4
(start of next word)
Continue scanning:
j = 4
: 'i' is not a space, continuej = 5
: 's' is the last character (j == n-1
), reverse word fromi=4
toj=5
- Reverse ['i','s'] β ['s','i']
- Array becomes:
['t','a','c',' ','s','i']
After Step 1:
['t','a','c',' ','s','i'] 0 1 2 3 4 5
Step 2: Reverse the entire array
Reverse from index 0 to 5:
- Swap positions 0β5: 't'β'i' β
['i','a','c',' ','s','t']
- Swap positions 1β4: 'a'β's' β
['i','s','c',' ','a','t']
- Swap positions 2β3: 'c'β' ' β
['i','s',' ','c','a','t']
Final Result:
['i','s',' ','c','a','t']
The original "cat is" has been transformed to "is cat" - words reversed while maintaining character order within each word!
Solution Implementation
1class Solution:
2 def reverseWords(self, s: List[str]) -> None:
3 """
4 Reverses the order of words in a character array in-place.
5 First reverses each individual word, then reverses the entire array.
6
7 Args:
8 s: List of characters representing words separated by spaces
9 """
10
11 def reverse(start: int, end: int) -> None:
12 """
13 Helper function to reverse a portion of the array in-place.
14
15 Args:
16 start: Starting index of the portion to reverse
17 end: Ending index of the portion to reverse
18 """
19 while start < end:
20 # Swap characters at start and end positions
21 s[start], s[end] = s[end], s[start]
22 start += 1
23 end -= 1
24
25 # Initialize starting position and get array length
26 word_start = 0
27 array_length = len(s)
28
29 # Iterate through each character in the array
30 for current_index, char in enumerate(s):
31 if char == " ":
32 # Found a space, reverse the word before it
33 reverse(word_start, current_index - 1)
34 # Update starting position for next word
35 word_start = current_index + 1
36 elif current_index == array_length - 1:
37 # Reached the last character, reverse the last word
38 reverse(word_start, current_index)
39
40 # Reverse the entire array to get words in reverse order
41 reverse(0, array_length - 1)
42
1class Solution {
2 /**
3 * Reverses the order of words in a character array.
4 * First reverses each individual word, then reverses the entire array.
5 * Example: "the sky is blue" -> "blue is sky the"
6 *
7 * @param s the character array containing words separated by spaces
8 */
9 public void reverseWords(char[] s) {
10 int length = s.length;
11 int wordStart = 0;
12
13 // Iterate through the character array
14 for (int currentIndex = 0; currentIndex < length; currentIndex++) {
15 // Check if we've reached a space (end of current word)
16 if (s[currentIndex] == ' ') {
17 // Reverse the current word (from wordStart to currentIndex - 1)
18 reverse(s, wordStart, currentIndex - 1);
19 // Update wordStart to the beginning of the next word
20 wordStart = currentIndex + 1;
21 }
22 // Check if we've reached the last character (end of last word)
23 else if (currentIndex == length - 1) {
24 // Reverse the last word (from wordStart to currentIndex)
25 reverse(s, wordStart, currentIndex);
26 }
27 }
28
29 // Reverse the entire array to get words in reversed order
30 reverse(s, 0, length - 1);
31 }
32
33 /**
34 * Helper method to reverse a portion of the character array in-place.
35 * Uses two-pointer technique to swap characters from both ends.
36 *
37 * @param s the character array to be modified
38 * @param startIndex the starting index of the portion to reverse (inclusive)
39 * @param endIndex the ending index of the portion to reverse (inclusive)
40 */
41 private void reverse(char[] s, int startIndex, int endIndex) {
42 // Swap characters from both ends moving towards the center
43 while (startIndex < endIndex) {
44 // Swap characters at startIndex and endIndex
45 char temp = s[startIndex];
46 s[startIndex] = s[endIndex];
47 s[endIndex] = temp;
48
49 // Move pointers towards the center
50 startIndex++;
51 endIndex--;
52 }
53 }
54}
55
1class Solution {
2public:
3 void reverseWords(vector<char>& s) {
4 // Lambda function to reverse characters between indices left and right (inclusive)
5 auto reverseRange = [&](int left, int right) {
6 while (left < right) {
7 swap(s[left], s[right]);
8 left++;
9 right--;
10 }
11 };
12
13 int n = s.size();
14 int wordStart = 0;
15
16 // First pass: Reverse each individual word
17 for (int current = 0; current < n; current++) {
18 // When we encounter a space, reverse the word before it
19 if (s[current] == ' ') {
20 reverseRange(wordStart, current - 1);
21 wordStart = current + 1; // Next word starts after the space
22 }
23 // When we reach the last character, reverse the last word
24 else if (current == n - 1) {
25 reverseRange(wordStart, current);
26 }
27 }
28
29 // Second pass: Reverse the entire string to get words in reverse order
30 reverseRange(0, n - 1);
31 }
32};
33
1/**
2 * Reverses the order of words in a character array in-place.
3 * Do not return anything, modify s in-place instead.
4 * @param s - Character array to be modified
5 */
6function reverseWords(s: string[]): void {
7 const arrayLength: number = s.length;
8
9 /**
10 * Helper function to reverse a portion of the array between two indices
11 * @param startIndex - Starting index (inclusive)
12 * @param endIndex - Ending index (inclusive)
13 */
14 const reverseSegment = (startIndex: number, endIndex: number): void => {
15 // Swap characters from both ends moving towards the center
16 while (startIndex < endIndex) {
17 // Swap characters using destructuring assignment
18 [s[startIndex], s[endIndex]] = [s[endIndex], s[startIndex]];
19 startIndex++;
20 endIndex--;
21 }
22 };
23
24 // First pass: Reverse each individual word
25 let wordStartIndex: number = 0;
26
27 for (let currentIndex: number = 0; currentIndex <= arrayLength; currentIndex++) {
28 // Check if we've reached a space (word boundary) or the end of array
29 if (currentIndex === arrayLength || s[currentIndex] === ' ') {
30 // Reverse the current word (excluding the space)
31 reverseSegment(wordStartIndex, currentIndex - 1);
32 // Move word start to the character after the space
33 wordStartIndex = currentIndex + 1;
34 }
35 }
36
37 // Second pass: Reverse the entire array to get words in reverse order
38 reverseSegment(0, arrayLength - 1);
39}
40
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the character array s
.
The algorithm consists of two main parts:
-
First loop through the array to reverse individual words: This iterates through each character exactly once (
O(n)
), and for each word encountered, it reverses that word. The reversal operation for each word takes time proportional to the word's length. Since each character is involved in exactly one reversal operation (as part of its word), the total work done across all word reversals isO(n)
. -
Final reversal of the entire array: This performs
n/2
swaps, which isO(n)
.
Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Variables
i
,j
,n
, andc
for iteration and tracking positions - The
reverse
helper function uses local variablesi
andj
(shadows the outer scope) - All operations are performed in-place on the input array
No additional data structures are created that scale with the input size, resulting in constant space complexity.
Common Pitfalls
1. Forgetting to Handle the Last Word
One of the most common mistakes is forgetting to reverse the last word in the array. Since the last word doesn't have a trailing space, the condition char == " "
won't trigger for it.
Incorrect approach:
for current_index, char in enumerate(s):
if char == " ":
reverse(word_start, current_index - 1)
word_start = current_index + 1
# Forgot to handle the last word!
Solution: Add an explicit check for the last character:
for current_index, char in enumerate(s):
if char == " ":
reverse(word_start, current_index - 1)
word_start = current_index + 1
elif current_index == array_length - 1: # Don't forget this!
reverse(word_start, current_index)
2. Incorrect Boundary Indices When Reversing
Another frequent error is using wrong indices when calling the reverse function, especially off-by-one errors.
Common mistakes:
- Using
reverse(word_start, current_index)
instead ofreverse(word_start, current_index - 1)
when a space is found - Using
reverse(word_start, current_index - 1)
instead ofreverse(word_start, current_index)
for the last word
Solution:
Remember that when you encounter a space at index j
, the word ends at index j-1
. But when processing the last character (which is part of a word), include it in the reversal.
3. Handling Edge Cases with Multiple Spaces
The problem states words are separated by exactly one space, but if the input could have multiple consecutive spaces or leading/trailing spaces, the current solution would fail.
Issue with multiple spaces:
# Input: ['a', ' ', ' ', 'b'] # Current algorithm would try to reverse empty "words" between spaces
Solution for robust handling:
def reverseWords(self, s: List[str]) -> None:
def reverse(start: int, end: int) -> None:
while start < end:
s[start], s[end] = s[end], s[start]
start += 1
end -= 1
word_start = 0
in_word = False
for i in range(len(s)):
if s[i] != ' ':
if not in_word:
word_start = i
in_word = True
else:
if in_word:
reverse(word_start, i - 1)
in_word = False
# Handle last word if array doesn't end with space
if in_word:
reverse(word_start, len(s) - 1)
reverse(0, len(s) - 1)
4. Modifying the Loop Variable Inside the Loop
Some might try to manually increment the index variable, which can lead to skipped characters or index out of bounds errors.
Incorrect approach:
j = 0
while j < len(s):
if s[j] == " ":
reverse(i, j - 1)
i = j + 1
j = j + 2 # Trying to skip the space - DON'T DO THIS
j += 1
Solution:
Use enumerate()
or a simple for loop that handles iteration automatically, avoiding manual index manipulation.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!