Facebook Pixel

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:

  1. Move i forward until it finds an English letter
  2. Move j backward until it finds an English letter
  3. Swap the letters at positions i and j
  4. Continue this process until the pointers meet

This ensures that only letters are swapped while non-letter characters remain fixed in their original positions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 string
  • j = 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:

  1. Find the next letter from the left: The first inner while loop moves pointer i forward, skipping any non-alphabetic characters. The condition while i < j and not cs[i].isalpha() ensures we stop either when we find a letter or when the pointers meet.

  2. Find the next letter from the right: The second inner while loop moves pointer j 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.

  3. 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.

  4. Move pointers inward: After swapping, we increment i and decrement j 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 Evaluator

Example 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, move i forward β†’ i = 2
  • Check cs[2] = 'b': It's a letter βœ“
  • Check cs[3] = '$': Not a letter, move j backward β†’ j = 2
  • Now i = 2 and j = 2, so i < 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 pointer j
  • 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"
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More