Reverse Only Letters
EasyPrompt
Given a string s, reverse the string according to the following rules:
- All the characters that are not English letters remain in the same position.
- All the English letters (lowercase or uppercase) should be reversed.
Return s after reversing it.
Example 1:
Input: s = "ab-cd"
Output: "dc-ba"Example 2:
Input: s = "a-bC-dEf-ghIj"
Output: "j-Ih-gfE-dCba"Example 3:
Input: s = "Test1ng-Leet=code-Q!"
Output: "Qedo1ct-eeLg=ntse-T!"
Constraints:
1 <= s.length <= 100sconsists of characters with ASCII values in the range[33, 122].sdoes not contain'\"'or'\\'.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves three main steps. First, we iterate through the input string to extract all the English letters and store them in a separate data structure, like a list or a StringBuilder. Second, we reverse this collection of letters. Finally, we build a new string by iterating through the original string one more time. If a character is a letter, we append the next letter from our reversed collection; otherwise, we append the non-letter character as is.
Algorithm
- Create a
StringBuilderor a list to store only the letters from the input strings. - Iterate through
s. For each character, check if it's an English letter usingCharacter.isLetter(). - If it is a letter, append it to the
StringBuilder/list. - After the first pass, reverse the
StringBuilder/list of letters. - Create a new
StringBuilderfor the final result. - Initialize a pointer/index for the reversed letters list to 0.
- Iterate through the original string
sagain. For each character: - If the character is a letter, append the character from the reversed letters list at the current pointer, and increment the pointer.
- If the character is not a letter, append it directly to the result.
- Convert the result
StringBuilderto a string and return it.
Walkthrough
The core idea is to separate the letters from the non-letters. We can make one pass through the string to collect all the letters into an auxiliary data structure. Once collected, we can easily reverse this collection. Then, we make a second pass through the original string to construct our final answer. When we encounter a position that originally held a letter, we take the next available letter from our reversed collection. When we encounter a non-letter, we place it back in its original position.
class Solution { public String reverseOnlyLetters(String s) { StringBuilder letters = new StringBuilder(); for (char c : s.toCharArray()) { if (Character.isLetter(c)) { letters.append(c); } } letters.reverse(); StringBuilder result = new StringBuilder(); int letterIndex = 0; for (char c : s.toCharArray()) { if (Character.isLetter(c)) { result.append(letters.charAt(letterIndex)); letterIndex++; } else { result.append(c); } } return result.toString(); }}Complexity
Time
O(N), where N is the length of the string. We perform two separate passes over the string, and reversing the list of letters takes time proportional to the number of letters (L). The total time is O(N) + O(L) + O(N), which simplifies to O(N) as L <= N.
Space
O(N), where N is the length of the string. We need extra space to store the collected letters, which can be up to N in the worst case. We also need space for the result `StringBuilder`, which is also O(N). Thus, the total auxiliary space is O(N).
Trade-offs
Pros
Conceptually straightforward and easy to implement.
Separates the logic of finding letters and placing them, which can be easier to debug.
Cons
Requires extra space proportional to the number of letters in the string.
Requires two passes over the input string, which is less efficient than a single-pass solution.
A more efficient approach uses two pointers, one starting from the beginning of the string (left) and the other from the end (right). The pointers move towards each other, skipping over any non-letter characters. When both pointers have found a letter, the characters at these two positions are swapped. This process continues until the pointers meet or cross, effectively reversing the letters in a single pass while leaving non-letter characters untouched.
Algorithm
- Convert the input string
sinto a character arraycharsto allow for in-place modification. - Initialize two pointers:
left = 0andright = chars.length - 1. - Start a loop that continues as long as
left < right. - Inside the loop, move the
leftpointer forward until it points to a letter (while (left < right && !Character.isLetter(chars[left])) { left++; }). - Similarly, move the
rightpointer backward until it points to a letter (while (left < right && !Character.isLetter(chars[right])) { right--; }). - Once both pointers are at letters, swap the characters at
chars[left]andchars[right]. - After the swap, move both pointers towards the center:
left++andright--. - After the loop finishes, convert the modified character array
charsback into a string and return it.
Walkthrough
This method is a classic in-place reversal technique adapted for this specific problem. By converting the string to a character array, we can modify it directly. The left pointer seeks the first letter from the start, and the right pointer seeks the first letter from the end. Once found, they are swapped. This ensures that the first letter is swapped with the last, the second with the second-to-last, and so on, achieving the reversal. Non-letters are simply skipped over by the pointers, so they remain in their original positions.
class Solution { public String reverseOnlyLetters(String s) { char[] chars = s.toCharArray(); int left = 0; int right = s.length() - 1; while (left < right) { // Find the next letter from the left while (left < right && !Character.isLetter(chars[left])) { left++; } // Find the next letter from the right while (left < right && !Character.isLetter(chars[right])) { right--; } // Swap the letters if (left < right) { char temp = chars[left]; chars[left] = chars[right]; chars[right] = temp; // Move pointers left++; right--; } } return new String(chars); }}Complexity
Time
O(N), where N is the length of the string. Each pointer, `left` and `right`, traverses the array at most once. Therefore, the total number of operations is proportional to N.
Space
O(N). Although the swap is done 'in-place' on the character array, creating this array from the immutable input string requires O(N) space. This is still an improvement over the first approach which required O(N) for the letters list in addition to O(N) for the result.
Trade-offs
Pros
More space-efficient as it avoids creating a separate list for letters.
Performs the reversal in a single pass over the string.
Cons
The logic of moving two pointers and swapping can be slightly more complex to grasp initially compared to the collect-and-rebuild method.
Solutions
Solution
class Solution {public String reverseOnlyLetters(String s) { char[] chars = s.toCharArray(); int i = 0, j = s.length() - 1; while (i < j) { while (i < j && !Character.isLetter(chars[i])) { ++i; } while (i < j && !Character.isLetter(chars[j])) { --j; } if (i < j) { char t = chars[i]; chars[i] = chars[j]; chars[j] = t; ++i; --j; } } return new String(chars); }}class Solution {public: string reverseOnlyLetters(string s) { int i = 0, j = s.size() - 1; while (i < j) { while (i < j && !isalpha(s[i])) ++i; while (i < j && !isalpha(s[j])) --j; if (i < j) { swap(s[i], s[j]); ++i; --j; } } return s; }};class Solution: def reverseOnlyLetters(self, s: str) -> str: s = list(s) i, j = 0, len(s) - 1 while i < j: while i < j and not s[i]. isalpha(): i += 1 while i < j and not s[j]. isalpha(): j -= 1 if i < j: s[i], s[j] = s[j], s[i] i, j = i + 1, j - 1 return '' . join(s)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.