The problem. Reverse an array of characters s in place: modify the input array, using O(1) extra memory.
s = ["h", "e", "l", "l", "o"] -> ["o", "l", "l", "e", "h"]
s = ["H", "a", "n", "n", "a", "h"] -> ["h", "a", "n", "n", "a", "H"]Reversing s[left..right] means swapping the two end letters and then reversing s[left + 1..right − 1] — which is the same problem on a range two letters shorter. The base case is a range with zero or one letters: nothing to do. Recursion expresses that in three lines — but watch the memory: n/2 calls pile up on the call stack, which breaks the O(1) memory rule.
Each call swaps the two ends and passes the middle to the next call.
function reverseString(s) { const reverse = (left, right) => { if (left >= right) return; // 0 or 1 letters left: done [s[left], s[right]] = [s[right], s[left]]; // swap the ends reverse(left + 1, right - 1); // reverse the middle }; reverse(0, s.length - 1);}Time O(n), space O(n) — n/2 frames on the call stack. Correct, but the problem asks for O(1) extra memory, and 100,000 letters would mean 50,000 frames: a stack overflow in Python.
The recursion is a tail call — nothing happens after it — so it turns straight into a loop: the parameters become two variables that move inwards.
function reverseString(s) { let left = 0, right = s.length - 1; while (left < right) { [s[left], s[right]] = [s[right], s[left]]; // swap the ends left++; // then move inwards right--; }}Time O(n), space O(1). The same steps without the call stack. This is the general lesson: a tail-recursive function always becomes a loop by turning its parameters into variables.