Reverse Vowels of a String
Reverse Vowels of a String: reverse only the vowels of s and return the result. Every consonant stays exactly where it is.
- 1 <= s.length <= 3 * 10⁵
- s consists of printable ASCII characters.
Intuition
Reversing a whole string is two pointers swapping from the ends inward. Here only the vowels take part, so keep the same walk but let each pointer skip past consonants until it lands on a vowel. Swap that pair, step inward, repeat — the consonants are never touched, so they cannot move.
This is the 'filtered two pointers' pattern: run the standard converging walk, but let each pointer skip anything that does not qualify. The same shape solves Valid Palindrome (skip non-alphanumerics), Sort Array By Parity, and Move Zeroes. Whenever a transformation applies to a subset of positions while the rest stay frozen, reach for pointers that skip rather than for extra arrays.
Approach
Before reading on: a full reversal swaps the ends and walks inward. What single change to that walk would make the consonants stay put? Aim for one pass and O(1) extra space.
Extract-and-refill is the obvious first idea
Collect every vowel into a list, reverse it, then walk the string again and replace each vowel position with the next value from the reversed list. It is easy to reason about and runs in O(n), which is optimal. The cost is a second O(n) array holding the vowels, plus two full passes. Correct and worth mentioning — but you can achieve the same result in one pass with no extra storage.
Two pointers that skip consonants
Put left at the start and right at the end. Advance left while it points at a consonant; retreat right while it points at a consonant. When both stop, each sits on a vowel, and those two vowels are the outermost unprocessed pair — so they are exactly the pair that must swap in a reversal. Swap them, move both pointers one step inward, and continue while left < right. Each index is visited at most once by one pointer, so the whole scan is O(n) despite the inner skip loops.
Why the swap order is automatically correct
A reversal maps the first vowel to the last position, the second to the second-last, and so on. The two-pointer walk produces exactly those pairings: the k-th vowel from the left meets the k-th vowel from the right. When the pointers cross, every pair has been swapped once. An odd number of vowels leaves the middle one with both pointers on it — the loop condition left < right stops before swapping it with itself, which is correct since the middle element of a reversal does not move.
Solution & live demo
Common pitfalls
Forgetting uppercase vowels
vowels = set("aeiou")vowels = set("aeiouAEIOU")The problem states vowels appear in both cases. With only lowercase in the set, "AEIOU" is treated as five consonants and returned unchanged — a silent wrong answer that passes casual testing on lowercase inputs.
Advancing both pointers on a skip
if chars[left] not in vowels and chars[right] not in vowels:
left += 1
right -= 1if chars[left] not in vowels:
left += 1
elif chars[right] not in vowels:
right -= 1Combining the conditions means a consonant on one side only moves when the other side is also a consonant, so a vowel on the left can be skipped past while waiting. Handle each side independently with elif so exactly one pointer moves per iteration.
Mutating a Python string directly
s[left], s[right] = s[right], s[left]
chars = list(s) chars[left], chars[right] = chars[right], chars[left]
Python strings are immutable, so item assignment raises TypeError. Convert to a list, swap in place, and join at the end — which is also why the C++ and Java versions can operate on the buffer directly.
Edge cases
Both pointers skip the entire string, cross immediately, and the original is returned unchanged.
The pointers meet on the same index; left < right fails so no swap occurs, which is right — a single element reversed is itself.
The vowel set must contain both cases; the problem counts uppercase vowels too.
No skipping happens and it degenerates to an ordinary full-string reversal.
Skip loops simply do not advance, and the neighbouring vowels swap normally.