917. Reverse Only Letters
Problem Description
You are given a string s
that contains various characters. Your task is to reverse only the English letters in the string while keeping all other characters in their original positions.
The rules are:
- Characters that are not English letters (such as numbers, spaces, punctuation marks, special symbols) must stay exactly where they are in the original string
- All English letters (both lowercase 'a-z' and uppercase 'A-Z') should be reversed in order
For example, if you have a string like "a-bC-dEf-ghIj"
, the English letters in order are [a, b, C, d, E, f, g, h, I, j]
. After reversing just these letters, you get [j, I, h, g, f, E, d, C, b, a]
. Placing them back while keeping non-letters in position gives you "j-Ih-gfE-dCba"
.
The solution uses a two-pointer technique. Starting with pointers i
at the beginning and j
at the end of the string, we:
- Move
i
forward until it finds an English letter - Move
j
backward until it finds an English letter - Swap the letters at positions
i
andj
- Continue this process until the pointers meet
This ensures that only letters are swapped while non-letter characters remain fixed in their original positions.
Intuition
When we need to reverse only specific elements while keeping others fixed, the natural approach is to think about how we can identify and swap just those elements. Since we want to reverse the order of letters, we need pairs of letters to swap - the first with the last, the second with the second-to-last, and so on.
The key insight is that we can use two pointers moving toward each other from opposite ends of the string. This is a classic pattern for reversing elements. However, the twist here is that not all characters should participate in the reversal - only the English letters.
So we adapt the standard two-pointer reversal technique by adding a filtering step: before each swap, we need to ensure both pointers are pointing to valid letters. If a pointer lands on a non-letter character, we simply skip over it and continue moving that pointer inward until we find a letter.
Think of it like having two workers, one starting from the left and one from the right. The left worker's job is to find letters moving rightward, and the right worker's job is to find letters moving leftward. When both workers have found a letter, they swap them. Any non-letter characters they encounter along the way are simply ignored and left in place.
This approach naturally preserves the positions of non-letter characters because we never touch them - we only swap letters with other letters. The time complexity is O(n)
because each character is visited at most once by each pointer, and the space complexity is O(n)
for creating a mutable copy of the string (since strings are immutable in Python).
Learn more about Two Pointers patterns.
Solution Approach
The implementation uses the two-pointer technique to efficiently reverse only the English letters in the string.
First, we convert the string s
into a list cs
because strings in Python are immutable, and we need to perform character swaps. This gives us a mutable data structure to work with.
We initialize two pointers:
i = 0
pointing to the start of the stringj = len(cs) - 1
pointing to the end of the string
The main logic runs in a while i < j
loop, which continues as long as the pointers haven't crossed each other:
-
Find the next letter from the left: The first inner
while
loop moves pointeri
forward, skipping any non-alphabetic characters. The conditionwhile i < j and not cs[i].isalpha()
ensures we stop either when we find a letter or when the pointers meet. -
Find the next letter from the right: The second inner
while
loop moves pointerj
backward, skipping any non-alphabetic characters. Similarly,while i < j and not cs[j].isalpha()
ensures we stop when we find a letter or when the pointers meet. -
Swap the letters: Once both pointers are positioned at letters (and
i < j
still holds), we swap the characters:cs[i], cs[j] = cs[j], cs[i]
. This Python syntax performs the swap in one line. -
Move pointers inward: After swapping, we increment
i
and decrementj
to move both pointers one step closer to each other:i, j = i + 1, j - 1
.
Finally, we join the list back into a string using "".join(cs)
and return the result.
The beauty of this approach is that non-letter characters never move - they're simply skipped over by the pointers. Only letters get swapped with other letters, achieving the desired reversal while maintaining the positions of all other characters.
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 trace through the algorithm with the string "a2b$c"
.
Initial Setup:
- Convert string to list:
cs = ['a', '2', 'b', '$', 'c']
- Set pointers:
i = 0
(at 'a'),j = 4
(at 'c')
Iteration 1:
- Check
cs[0]
= 'a': It's a letter β - Check
cs[4]
= 'c': It's a letter β - Swap 'a' and 'c':
cs = ['c', '2', 'b', '$', 'a']
- Move pointers:
i = 1
,j = 3
Iteration 2:
- Check
cs[1]
= '2': Not a letter, movei
forward βi = 2
- Check
cs[2]
= 'b': It's a letter β - Check
cs[3]
= '$': Not a letter, movej
backward βj = 2
- Now
i = 2
andj = 2
, soi < j
is false. Loop ends.
Result:
- Join list back to string:
"c2b$a"
- The letters 'a', 'b', 'c' have been reversed to 'c', 'b', 'a'
- The non-letters '2' and '$' stayed in positions 1 and 3
The algorithm successfully reversed only the English letters while keeping '2' and '$' in their original positions.
Solution Implementation
1class Solution:
2 def reverseOnlyLetters(self, s: str) -> str:
3 """
4 Reverses only the alphabetic characters in a string while keeping
5 non-alphabetic characters in their original positions.
6
7 Args:
8 s: Input string to process
9
10 Returns:
11 String with only alphabetic characters reversed
12 """
13 # Convert string to list for in-place character swapping
14 char_list = list(s)
15
16 # Initialize two pointers at the start and end of the list
17 left = 0
18 right = len(char_list) - 1
19
20 # Process until the two pointers meet
21 while left < right:
22 # Move left pointer forward until we find an alphabetic character
23 while left < right and not char_list[left].isalpha():
24 left += 1
25
26 # Move right pointer backward until we find an alphabetic character
27 while left < right and not char_list[right].isalpha():
28 right -= 1
29
30 # If both pointers are at alphabetic characters, swap them
31 if left < right:
32 char_list[left], char_list[right] = char_list[right], char_list[left]
33 left += 1
34 right -= 1
35
36 # Convert the list back to string and return
37 return "".join(char_list)
38
1class Solution {
2 public String reverseOnlyLetters(String s) {
3 // Convert string to character array for in-place manipulation
4 char[] charArray = s.toCharArray();
5
6 // Initialize two pointers: left starting from beginning, right from end
7 int left = 0;
8 int right = charArray.length - 1;
9
10 // Continue swapping while pointers haven't crossed
11 while (left < right) {
12 // Move left pointer forward until we find a letter
13 while (left < right && !Character.isLetter(charArray[left])) {
14 left++;
15 }
16
17 // Move right pointer backward until we find a letter
18 while (left < right && !Character.isLetter(charArray[right])) {
19 right--;
20 }
21
22 // If both pointers point to letters, swap them
23 if (left < right) {
24 // Swap characters at left and right positions
25 char temp = charArray[left];
26 charArray[left] = charArray[right];
27 charArray[right] = temp;
28
29 // Move both pointers inward for next iteration
30 left++;
31 right--;
32 }
33 }
34
35 // Convert character array back to string and return
36 return new String(charArray);
37 }
38}
39
1class Solution {
2public:
3 string reverseOnlyLetters(string s) {
4 // Initialize two pointers: left starts from beginning, right from end
5 int left = 0;
6 int right = s.size() - 1;
7
8 // Continue swapping while pointers haven't crossed
9 while (left < right) {
10 // Move left pointer forward until we find a letter
11 while (left < right && !isalpha(s[left])) {
12 ++left;
13 }
14
15 // Move right pointer backward until we find a letter
16 while (left < right && !isalpha(s[right])) {
17 --right;
18 }
19
20 // If both pointers point to letters, swap them and move both pointers
21 if (left < right) {
22 swap(s[left], s[right]);
23 ++left;
24 --right;
25 }
26 }
27
28 // Return the modified string with only letters reversed
29 return s;
30 }
31};
32
1/**
2 * Reverses only the letters in a string while keeping non-letter characters in their original positions
3 * @param s - The input string to process
4 * @returns The string with only letters reversed
5 */
6function reverseOnlyLetters(s: string): string {
7 // Convert string to array of characters for in-place manipulation
8 const characters: string[] = [...s];
9
10 // Initialize two pointers: left pointer at start, right pointer at end
11 let leftIndex: number = 0;
12 let rightIndex: number = characters.length - 1;
13
14 // Process until the two pointers meet
15 while (leftIndex < rightIndex) {
16 // Move left pointer forward until we find a letter or pointers meet
17 while (!/[a-zA-Z]/.test(characters[leftIndex]) && leftIndex < rightIndex) {
18 leftIndex++;
19 }
20
21 // Move right pointer backward until we find a letter or pointers meet
22 while (!/[a-zA-Z]/.test(characters[rightIndex]) && leftIndex < rightIndex) {
23 rightIndex--;
24 }
25
26 // Swap the letters at the current positions
27 [characters[leftIndex], characters[rightIndex]] = [characters[rightIndex], characters[leftIndex]];
28
29 // Move both pointers inward for the next iteration
30 leftIndex++;
31 rightIndex--;
32 }
33
34 // Join the character array back into a string and return
35 return characters.join('');
36}
37
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses a two-pointer approach where i
starts from the beginning and j
starts from the end of the string. In the worst case:
- Each character in the string is visited at most twice: once by the left pointer
i
and once by the right pointerj
- The outer while loop continues until
i >= j
, meaning the pointers meet in the middle - Each inner while loop advances its respective pointer by skipping non-alphabetic characters
- The swap operation when both pointers point to letters takes
O(1)
time
Since each element is processed a constant number of times, the total time complexity is O(n)
where n
is the length of the string.
Space Complexity: O(n)
The algorithm creates a list cs
from the input string s
using list(s)
, which requires O(n)
additional space to store all characters. The remaining variables (i
, j
) use only O(1)
space. Therefore, the overall space complexity is O(n)
where n
is the length of the string.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check Pointer Bounds in Inner Loops
One of the most common mistakes is omitting the left < right
condition in the inner while loops. Without this check, the pointers could cross each other while searching for alphabetic characters, leading to incorrect swaps or unnecessary iterations.
Incorrect Code:
# Missing boundary check - pointers could cross! while not char_list[left].isalpha(): left += 1 while not char_list[right].isalpha(): right -= 1
Why It's a Problem:
- If the string has many non-alphabetic characters clustered together, one pointer might skip past the other
- This could cause IndexError if a pointer goes out of bounds
- Even with bounds checking elsewhere, crossed pointers lead to incorrect character swaps
Solution: Always include the pointer comparison in the loop conditions:
while left < right and not char_list[left].isalpha(): left += 1 while left < right and not char_list[right].isalpha(): right -= 1
2. Unnecessary Condition Check Before Swapping
The code shows if left < right:
before the swap operation, but this check is redundant since the outer while loop and inner loops already guarantee left < right
when both pointers are at alphabetic characters.
Current Code (with redundant check):
if left < right: # This is actually unnecessary char_list[left], char_list[right] = char_list[right], char_list[left] left += 1 right -= 1
Better Approach:
# Swap directly - we already know left < right from the loop conditions char_list[left], char_list[right] = char_list[right], char_list[left] left += 1 right -= 1
3. Incorrect Pointer Movement After Swap
A subtle mistake is forgetting to move both pointers after performing a swap, or moving them incorrectly.
Incorrect Examples:
# Forgetting to move pointers - causes infinite loop char_list[left], char_list[right] = char_list[right], char_list[left] # Moving only one pointer - causes incorrect behavior char_list[left], char_list[right] = char_list[right], char_list[left] left += 1 # Missing right -= 1
Solution: Always move both pointers inward after a successful swap:
char_list[left], char_list[right] = char_list[right], char_list[left] left += 1 right -= 1
4. Edge Case: Strings with No Alphabetic Characters
While the current solution handles this correctly, it's worth noting that strings containing only non-alphabetic characters (like "123-456"
or "---"
) could be overlooked during testing. The algorithm should gracefully handle these cases without attempting any swaps.
Test Cases to Remember:
- Empty string:
""
- Only non-letters:
"123!@#"
- Only letters:
"abcDEF"
- Single character:
"a"
or"!"
- Mixed with spaces:
"a b c"
A heap is a ...?
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!