Reverse Vowels of a String
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.
If chars[left] isn't a vowel, advance left - consonants never move.
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.
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
Same bound as the letters variant - every character is visited by exactly
one pointer, and a Set.has lookup is O(1).
Dominated by s.split(""), since JS strings are immutable - the fixed
10-entry vowel Set itself is O(1) regardless of input size.
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
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.
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.
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.