RezloRezloPrep
Sign Up Free
Two Pointer PatternLeetCode 345

Reverse Vowels of a String

6 min readEasyPredicate Swap
Reverse just the vowels in a string in place, relative to each other - the same skip-and-swap two-pointer pattern as Reverse Only Letters, with the predicate swapped from 'is a letter' to 'is a vowel'.

Problem Statement

Given a string s, reverse only the vowels in it and return the result. Vowels are a, e, i, o, u, in both lower and upper case, and can appear more than once.

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

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

Approach: Same Skeleton, Different Predicate

This is Reverse Only Letters with one line changed: instead of skipping non-letters, skip anything that isn't a vowel.

left skips forward over consonants

If chars[left] isn't a vowel, advance left - consonants never move.

right skips backward over consonants

If chars[right] isn't a vowel, pull right in - same idea from the tail end.

Once both pointers land on vowels, swap them and step both inward. A Set of the ten vowel characters (both cases) makes the membership check a constant-time lookup instead of a regex test per character.

reverse_vowels.js
JavaScript
function reverseVowels(s) {
const vowels = new Set(["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"]);
const chars = s.split("");
let left = 0;
let right = chars.length - 1;

while (left < right) {
if (!vowels.has(chars[left])) {
left++;
} else if (!vowels.has(chars[right])) {
right--;
} else {
[chars[left], chars[right]] = [chars[right], chars[left]];
left++;
right--;
}
}

return chars.join("");
}

console.log(reverseVowels("leetcode")); // "leotcede"

Complexity Analysis

Time Complexity: O(n)

Same bound as the letters variant - every character is visited by exactly one pointer, and a Set.has lookup is O(1).

Space Complexity: O(n)

Dominated by s.split(""), since JS strings are immutable - the fixed 10-entry vowel Set itself is O(1) regardless of input size.

Common Mistake

Building the vowel set with only lowercase characters and forgetting the uppercase forms is the most common slip - "IceCreAm" has three uppercase vowels in it, and missing them silently produces the wrong output instead of an error, which makes it easy to overlook in a quick test run.

How to Explain it in an Interview

The 30-Second Spoken Pitch
"Once you've built the skip-and-swap two-pointer pattern for reversing only letters, this is a one-line change: swap the letter-membership check for a vowel-membership check against a small Set. Same two pointers walking inward, same swap-when-both-match logic - just a different predicate deciding what counts as a match."
If the Interviewer Probes Deeper:
Why a Set instead of an array with .includes()?

Functionally both work for ten elements, but Set.has is O(1) and signals the intent - membership testing - more clearly than .includes(), which reads as a linear search even when it isn't the bottleneck here.

How would you generalize this to reverse characters matching any predicate?

Extract the membership check into a parameter - reverseMatching(s, isTarget) - and the two-pointer skeleton doesn't change at all. That's the real takeaway from doing all three of these problems back to back: the pattern is reusable, only the predicate changes.

Does the order of vowels matter, or just their positions?

Their positions - the vowels are reversed relative to each other, not sorted or altered in any other way. The first vowel encountered from the left ends up where the last vowel from the right was, and vice versa.