Mastering LeetCode Problem 0345

Problem 0345: Reverse Vowels of a String

When solving string problems on LeetCode, you’ll notice a recurring pattern:
two pointers moving towards each other from the ends of the string.

LeetCode 345 – Reverse Vowels of a String – is a beautiful, focused exercise on this pattern.
It’s simpler than many “two-pointer” problems, yet rich enough to train your intuition for more advanced challenges.

In this post, we’ll break down the problem, discuss naive and optimal ideas, and then walk step-by-step through an elegant two-pointer solution—aligned with the exact logic you implemented.


1. Problem Statement

You are given a string s.
Your task is to reverse only the vowels in the string and return the resulting string.

  • Vowels are: a, e, i, o, u, A, E, I, O, U
  • Consonants, digits, and symbols must stay in their original positions.
  • You only change the positions of vowels, by reversing their order.

1.1. Example 1

Input:  s = "hello"
Output: "holle"

Vowels: 'e' and 'o'
Reversing them: "holle"

1.2. Example 2

Input:  s = "leetcode"
Output: "leotcede"

Vowels in order: e, e, o, e
Reversed vowels: e, o, e, e
Final string: "leotcede"

Example 3

Input:  s = "IceCreAm"
Output: "AceCreIm"

Vowels (in order): I, e, e, A
Reversed: A, e, e, I
Consonants remain in place; only vowels are reordered.

2. First Thoughts – A Straightforward (But Less Elegant) Approach

The most direct idea might look like this:

  1. Traverse the string and collect all vowels in a list.
  2. Reverse that list of vowels.
  3. Traverse the string again:
    • Every time you see a vowel, replace it with the next vowel from the reversed list.

This works.
But what are the costs?

  • You make multiple passes over the string.
  • You maintain a list of all vowels.
  • There’s extra management for indices into that reversed list.

Time complexity is still O(n) and space complexity is also O(n), but the solution feels less direct and less “in-place”.

This is fine as a first attempt. However, there’s a cleaner and more “interview-friendly” pattern we can apply.


3. The Two-Pointer Idea

Two-pointer problems on strings or arrays usually come in one of these flavors:

  • Same direction: both pointers move forward (e.g., slow & fast pointer).
  • Opposite directions: one pointer from the start, one from the end.

This problem is clearly in the second category.

Here’s the high-level idea:

  1. Convert the string to a mutable list of characters (since Python strings are immutable).
  2. Maintain two indices:
    • left starting at the beginning (0)
    • right starting at the end (len(s) - 1)
  3. While left < right:
    • Move left to the right until it lands on a vowel.
    • Move right to the left until it lands on a vowel.
    • If both s[left] and s[right] are vowels:
      • Swap them.
      • Move both pointers inward.

By doing this, we effectively reverse only the vowels while leaving all other characters untouched.

This gives us:

  • Single pass with two pointers → O(n) time.
  • Only a list conversion overhead → O(n) space (standard for in-place swaps in Python).

4. Implementation – Two Pointers, In-Place on a List

Let’s look at the core logic in Python, matching the algorithm you implemented.

Note: For LeetCode’s expected signature, you’d add self as the first parameter, but here we focus on the core idea.

class Solution:
    def reverseVowels(s: str) -> str:
        
        vowels = set("aeiouAEIOU")
        s = list(s)

        left, right = 0, len(s) - 1

        while left < right:

            if s[left] not in vowels:
                left += 1
                continue

            if s[right] not in vowels:
                right -= 1
                continue

            s[left], s[right] = s[right], s[left]

            left += 1
            right -= 1

        return "".join(s)

We’ll now dissect this line by line.


5. Detailed Walkthrough

5.1. Defining the Vowel Set and Converting String to List

vowels = set("aeiouAEIOU")
s = list(s)
  • vowels is a set of characters for O(1) lookup.
  • Both lowercase and uppercase vowels are covered.
  • s = list(s) converts the string to a list of characters so we can swap in-place.

5.2. Initializing the Two Pointers

left, right = 0, len(s) - 1
  • left starts from the beginning.
  • right starts from the end.

We will process the string from both ends towards the middle.

5.3. Main Loop

while left < right:

We continue as long as left is strictly less than right.
When left >= right, all needed swaps are done.

Inside this loop, we have three key components.


5.3.1. Move left Until It Reaches a Vowel

if s[left] not in vowels:
    left += 1
    continue
  • If s[left] is not a vowel:
    • Increment left and skip the rest of this iteration (continue).
  • This effectively means:
    • “Keep moving left forward until you find a vowel.”

5.3.2. Move right Until It Reaches a Vowel

if s[right] not in vowels:
    right -= 1
    continue
  • If s[right] is not a vowel:
    • Decrement right and skip the rest of this iteration.
  • Interpretation:
    • “Keep moving right backward until you find a vowel.”

5.3.3. Swap the Two Vowels

If we reach this part, it means:

  • s[left] is a vowel.
  • s[right] is a vowel.
s[left], s[right] = s[right], s[left]

left += 1
right -= 1

We:

  1. Swap the two vowels.
  2. Move left forward and right backward for the next pair.

After enough iterations, we have reversed the order of vowels in the string.


6. Example Walkthrough: "IceCreAm"

Let’s trace your implementation on the example:

Input:  "IceCreAm"

Initial setup:

s = ['I', 'c', 'e', 'C', 'r', 'e', 'A', 'm']
left = 0
right = 7

6.1. Iteration 1

  • s[left] = 'I' → vowel
  • s[right] = 'm' → not vowel → right -= 1right = 6

6.2. Iteration 2

  • left = 0, right = 6
  • s[left] = 'I' → vowel
  • s[right] = 'A' → vowel
  • Swap:
s[0], s[6] = s[6], s[0]  # 'I' <-> 'A'

Now:

s = ['A', 'c', 'e', 'C', 'r', 'e', 'I', 'm']
left = 1
right = 5

6.3. Iteration 3

  • s[1] = 'c' → not vowel → left = 2
  • s[2] = 'e' → vowel
  • s[5] = 'e' → vowel
  • Swap s[2] and s[5]:
    • Both are ‘e’, so string stays the same.
  • Move pointers:
left = 3
right = 4

6.4. Iteration 4

  • s[3] = 'C' → not vowel → left = 4
  • Now left = 4, right = 4 → loop condition left < right fails.

End of loop.

Final s:

['A', 'c', 'e', 'C', 'r', 'e', 'I', 'm']

Return:

"AceCreIm"

All vowels have been reversed while consonants stayed exactly where they were.


7. Time and Space Complexity

7.1. Time Complexity

  • Each pointer (left and right) moves at most n steps.
  • In each iteration, we do constant work:
    • Comparisons,
    • Pointer increments/decrements,
    • Occasional swap.

Therefore:

$\text{Time Complexity} = O(n)$

where ( n = \text{len}(s) ).

7.2. Space Complexity

  • Converting the string to a list uses O(n) extra space.
  • The vowels set is constant-sized: O(1).

So:

$\text{Space Complexity} = O(n)$

In Python, this is a very standard and acceptable approach for in-place modification of strings.


8. Edge Cases and How This Approach Handles Them

Let’s check some edge cases.

8.1. No Vowels

s = "bcdfg"
  • Both pointers move inward, but never find vowels to swap.
  • The string remains unchanged.
  • Our code behaves correctly.

8.2. All Vowels

s = "aeiou"
  • left at ‘a’, right at ‘u’ → swap → ‘u’, ‘e’, ‘i’, ‘o’, ‘a’
  • Then left and right move inward and swap ‘e’ and ‘o’.
  • Final result: "uoiea" → vowels fully reversed.

8.3. Single Character

s = "a" or s = "b"
  • left = 0, right = 0
  • left < right is false from the start.
  • We immediately return the string.

8.4. Empty String

s = ""
  • left = 0, right = -1
  • Loop never executes.
  • Returns the empty string.

8.5. Mix of Uppercase and Lowercase

Because our vowel set includes both lowercase and uppercase (aeiouAEIOU), cases like:

s = "AeIoU"

are also correctly handled.


9. Pattern Recognition – Why This Problem Matters

This problem is more than just vowel reversal. It trains a reusable mental template:

Two pointers moving from both ends, skipping irrelevant characters, and acting only on a specific subset.

You’ve already seen very similar ideas in:

  • LeetCode 125 – Valid Palindrome
    • Two pointers, skip non-alphanumeric characters, compare remaining ones.
  • LeetCode 344 – Reverse String
    • Two pointers, swap characters directly from both ends.
  • LeetCode 345 – Reverse Vowels of a String
    • Two pointers, but operate only when both sides land on vowels.

The core insight:

  • Filter & act pattern:
    • Skip non-target characters.
    • Apply logic when both sides are “interesting” (e.g., vowel, alphanumeric).
  • This generalizes to many string-processing questions.