Category: Easy | Concepts used: Two-pointer array swaps, String immutability, Heap heap space minimization
Problem Statement
Given a string, reverse it without using StringBuilder.reverse() or any other ready-made reverse method.
Input : "hello" Output: "olleh"
Input : "a" Output: "a"
Examples (with edge scenarios)
| # | Input | Output | Why |
|---|---|---|---|
| 1 | "hello" | "olleh" | Typical word |
| 2 | "" | "" | Empty string check |
| 3 | "a" | "a" | Single character is symmetric |
| 4 | "racecar" | "racecar" | Palindrome |
| 5 | "ab cd" | "dc ba" | Spaces treated as characters |
โ ๏ธ Common Beginner Mistake
Mistake Impact Fix Bypassing the check with new StringBuilder(str).reverse()Skips evaluating your logical loop capabilities Write the manual loop logic first, unless built-in methods are explicitly permitted Off-by-one errors during manual decrement loops Missing character boundaries, causing StringIndexOutOfBoundsExceptionSet the loop boundary index to start at str.length() - 1down to>= 0inclusive
Before You Code: Clarify the Contract
Before choosing an algorithm, confirm how null and empty strings should behave, whether comparison is case-sensitive, and whether spaces or punctuation count. Java char values are UTF-16 code units, not always complete human-visible Unicode characters, so international text may require code points or grapheme-aware libraries.
Analogy: Swapping Passenger Seats on a Train
Imagine a row of passengers sitting in numbered seats on a train:
- Solution 1 (Creating a new line): You ask everyone to stand up and form a new line starting from the back of the train, copying each passenger to their new seat one by one. If you do this with a stack of cards, you keep making copies of the entire stack every time you add a card, which is extremely slow.
- Solution 2 (Two-Pointer Swap): You have two supervisors: one starts at the front of the train (Left) and one at the back (Right).
- They point to two passengers and ask them to swap seats.
- The Left supervisor takes a step right (
left++), and the Right supervisor takes a step left (right--). - They swap the next pair.
- They repeat this until they meet in the middle. No one else has to move, and no extra train is needed!
Solution 1 โ Loop from the End, Build a New String (Basic)
This is the standard manual approach, iterating backward.
Intuition
By reading the string from the last index (length() - 1) back to index 0, we can append each character to a new result accumulator.
public class ReverseStringLoop {
public static String reverseString(String str) {
if (str == null || str.isEmpty()) {
return str;
}
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i); // Appending characters in reverse
}
return result;
}
public static void main(String[] args) {
System.out.println(reverseString("hello")); // "olleh"
System.out.println(reverseString("")); // ""
System.out.println(reverseString("a")); // "a"
}
}
Output:
olleh
a
Dry Run (str = โcatโ)
i = 2: ch = 't' -> result = "t"
i = 1: ch = 'a' -> result = "ta"
i = 0: ch = 'c' -> result = "tac"
Final result = "tac"
Solution 2 โ Using a char[] Array with Two Pointers (Optimal)
This is the most efficient manual approach, minimizing intermediate string copies.
Intuition
Java strings are immutable, so repeatedly adding to them using result += ch allocates a new string on every character. Instead, we can copy the string into a mutable character array once, swap elements in place from both ends, and convert it back to a string.
public class ReverseStringTwoPointer {
public static String reverseString(String str) {
if (str == null || str.isEmpty()) {
return str;
}
char[] chars = str.toCharArray(); // Mutable character representation
int left = 0;
int right = chars.length - 1;
while (left < right) {
// Swap character elements
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
// Move pointers inward
left++;
right--;
}
return new String(chars);
}
public static void main(String[] args) {
System.out.println(reverseString("hello")); // "olleh"
System.out.println(reverseString("ab cd")); // "dc ba"
}
}
Output:
olleh
dc ba
๐ Visual Flowchart
graph TD
Start["Given String S"] --> Empty{"S is null or empty?"}
Empty -->|Yes| RetSelf["Return S"]
Empty -->|No| ToArray["chars = S.toCharArray()"]
ToArray --> InitPtr["left = 0, right = chars.length - 1"]
InitPtr --> Compare{"left < right?"}
Compare -->|Yes| Swap["swap chars[left] and chars[right]"]
Swap --> Adjust["left++, right--"]
Adjust --> Compare
Compare -->|No| Convert["New String(chars)"]
Convert --> End["Return reversed string"]
Interviewer Insights
This is a core QA interview question designed to evaluate memory footprint awareness.
Follow-up questions you might get:
- โWhat is the time complexity of Solution 1?โ โ Solution 1 runs in (O(N^2)) time. Because strings are immutable, concatenating a character creates a new string copy. Copying a string of size (k) takes (O(k)) time. Summing this over (N) characters yields: Solution 2 operates in linear (O(N)) time because array swaps are (O(1)) memory modifications.
- โHow would you check if the string contains multi-byte Unicode characters (surrogate pairs)?โ โ Standard
charindexing can break surrogate pairs (e.g. emojis). For production support, you would need to iterate using Unicode code points (str.codePoints()).
Quick Recap
| Approach | Heap Allocation | Time Complexity | Auxiliary Space Complexity | Interview Signal |
|---|---|---|---|---|
Backward Loop (+=) | High (creates (N) strings) | (O(N^2)) | (O(N)) | Demonstrates basic logic, but shows poor memory awareness |
| Two-Pointer Swap | Low (only 1 char array) | (O(N)) | (O(N)) | Optimal manual swap, showing two-pointer pattern mastery |
StringBuilder.reverse() | Low | (O(N)) | (O(N)) | Concise, preferred only if built-ins are permitted |
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed