RezloRezloPrep
Sign Up Free
Two Pointer PatternLeetCode 917

Reverse Only Letters

7 min readEasySkip-and-Swap Variant
Reverse only the letters in a string while every non-letter character stays exactly where it started - the same two-pointer swap, with a skip condition added on each side.

Problem Statement

Given a string s, reverse it according to two rules:

  • Every character that is not an English letter stays in its original position.
  • Every English letter (upper or lower case) gets reversed relative to the other letters.
Input:  s = "ab-cd"
Output: "dc-ba"

Input:  s = "a-bC-dEf-ghIj"
Output: "j-Ih-gfE-dCba"

Approach: Two Pointers with a Skip Condition

This is Reverse String's exact two-pointer skeleton with one addition: before swapping, each pointer needs to check whether it's actually sitting on a letter.

left skips forward over non-letters

If chars[left] isn't a letter, advance left without touching it - its final position is already correct.

right skips backward over non-letters

Same idea from the other end: if chars[right] isn't a letter, pull right in without swapping.

Only once both pointers are parked on letters do you swap them and move both inward. Non-letters never move; letters get swapped pairwise from the outside in, exactly like the basic reversal.

reverse_only_letters.js
JavaScript
function reverseOnlyLetters(s) {
const chars = s.split("");
let left = 0;
let right = chars.length - 1;
const isLetter = (c) => /[a-zA-Z]/.test(c);

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

return chars.join("");
}

console.log(reverseOnlyLetters("ab-cd")); // "dc-ba"

Complexity Analysis

Time Complexity: O(n)

Both pointers move strictly inward on every iteration - whether it's a skip or a swap, some progress is made, so the loop runs at most n times total.

Space Complexity: O(n)

Unlike the in-place array version, s.split("") is required because JS string primitives are immutable - the character array is the one piece of extra memory this variant needs.

Common Mistake

Forgetting to advance the pointer inside the if/else if skip branches is the classic bug here - it produces an infinite loop, since left or right never moves past a non-letter. It's also easy to test letters with a lowercase-only check and silently mismatch on uppercase input; the regex /[a-zA-Z]/ (or /[a-z]/i) has to cover both cases.

How to Explain it in an Interview

The 30-Second Spoken Pitch
"It's the same two-pointer swap as basic string reversal, but each pointer independently skips over anything that isn't a letter before it's allowed to participate in a swap. That keeps every non-letter character pinned to its original index while the letters still get reversed relative to each other."
If the Interviewer Probes Deeper:
What if left and right are both non-letters at the same time?

Both branches are independent if/else if checks, so only one pointer advances per iteration in that case - the loop still terminates correctly, just one skip at a time rather than two.

How would you make this Unicode-aware, e.g. accented letters?

Swap the regex for a Unicode property escape like /\p{L}/u, which matches any Unicode letter category instead of just the ASCII A-Z/a-z range.

Could you solve this without converting to an array first?

Not in place - JS strings can't have individual characters reassigned, so some O(n) structure (array, or building a new string) is unavoidable here.

Next TopicReverse Vowels of a String