DSA Interview Guide · C++ · Two Pointers
String Compression: Brute Force to Optimal
LeetCode 443, solved three ways — a separate output array, an in-place two-pointer version using to_string, and a fully constant-space version with manual digit extraction — with intuition, C++ code, an animated dry run for each approach, and complexity analysis you can explain out loud.
- Problem statement
- Approach 1 — Brute force (extra output array)
- Approach 2 — Better (in-place, with to_string)
- Approach 3 — Optimal (fully constant space)
- Complexity comparison
- Interview notes
- FAQ
Problem Statement
Compress an array of characters in place: for each maximal run of consecutive identical characters, write the character once, followed by the run's length if it's greater than 1. Return the new length. Only constant extra space is allowed — the result must be written back into the input array itself.
Example: chars = ['a','a','b','b','c','c','c'] → new length 6, array becomes ['a','2','b','2','c','3',...].
To exercise the tricky part of this problem — counts with more than one digit — we'll trace all three approaches on chars = twelve 'a's, one 'b', two 'c's (15 characters total), which compresses to "a12bc2", a new length of 6.
Approach 1 · Brute Force
Build the Result in a Separate Array
Intuition
The most natural first solution ignores the space constraint entirely: scan through chars, identify each run of identical characters, and push the compressed representation — the character, then its count's digits if the run is longer than one — onto a brand-new output container. Once the scan is done, copy that container back over the front of chars.
It's correct and easy to verify, but it directly violates the problem's own requirement, since the separate output array is O(n) extra memory — worth writing down mentally, then immediately talking your way out of.
Algorithm
- Create an empty
result array. - Scan
chars left to right, grouping consecutive identical characters. - For each group, push the character onto
result; if the group's length is greater than 1, convert the length to a string and push each digit too. - Copy
result back into the front of chars. - Return
result.size().
C++ Code
int compress(vector<char>& chars) {
int n = chars.size();
vector<char> result;
int i = 0;
while (i < n) {
char ch = chars[i];
int start = i;
while (i < n && chars[i] == ch) i++;
int count = i - start;
result.push_back(ch);
if (count > 1) {
string cnt = to_string(count);
for (char digit : cnt) result.push_back(digit);
}
}
for (int k = 0; k < (int)result.size(); k++) chars[k] = result[k];
return result.size();
}
Dry Run
Complexity Analysis
| Metric |
Value |
Why |
| Time |
O(n) |
one scan to build the result, one pass to copy it back |
| Space |
O(n) |
the full compressed output is duplicated in a separate container first |
| Practical issue |
Violates the constraint |
the problem explicitly asks for constant extra space — this doesn't qualify |
Approach 2 · Better
Two Pointers, Writing In Place
Intuition
There's no real need for a separate array — the compressed output is never longer than the original input, so it can always be written back into chars itself, just behind where the scan currently is. Use a read pointer to find each group and a write pointer to place the compressed characters, and the whole compression happens in one pass with no auxiliary container.
Algorithm
- Keep
read and write pointers, both starting at 0. - At each step, note the current character and advance
read across the whole run of matching characters. - Write the character at
chars[write++]. - If the run's length is greater than 1, convert it with
to_string and write each digit at chars[write++]. - Continue until
read reaches the end; return write.
C++ Code
int compress(vector<char>& chars) {
int n = chars.size();
int read = 0, write = 0;
while (read < n) {
char ch = chars[read];
int start = read;
while (read < n && chars[read] == ch) read++;
int count = read - start;
chars[write++] = ch;
if (count > 1) {
string cnt = to_string(count);
for (char digit : cnt) chars[write++] = digit;
}
}
return write;
}
Dry Run
Complexity Analysis
| Metric |
Value |
Why |
| Time |
O(n) |
read and write pointers each move forward across the array once |
| Space |
O(1)* array space |
no auxiliary array — but to_string allocates a small temporary string per multi-digit group |
| Improvement |
In-place, single pass |
satisfies the spirit of the constraint, though not the strictest reading of it |
Approach 3 · Optimal
Fully In Place — No Strings, No Arrays
Intuition
Even the small string from to_string is unnecessary. A count's digits can be peeled off directly with % 10 and / 10 and written straight into chars — but that naturally produces them in reverse order (least significant digit first). The fix is simple: write them in that reversed order, then reverse just that small segment in place afterward so 12 ends up as '1', '2' instead of '2', '1'.
Algorithm
- Keep
read and write pointers, both starting at 0, exactly as before. - Find each run's character and count the same way.
- Write the character at
chars[write++]. - If the count is greater than 1, remember
digitStart = write, then repeatedly write '0' + count % 10 and divide count by 10 until it reaches 0. - Reverse
chars[digitStart .. write) in place to fix the digit order. - Continue until
read reaches the end; return write.
C++ Code
int compress(vector<char>& chars) {
int n = chars.size();
int read = 0, write = 0;
while (read < n) {
char ch = chars[read];
int start = read;
while (read < n && chars[read] == ch) read++;
int count = read - start;
chars[write++] = ch;
if (count > 1) {
int digitStart = write;
while (count > 0) {
chars[write++] = char('0' + count % 10);
count /= 10;
}
reverse(chars.begin() + digitStart, chars.begin() + write);
}
}
return write;
}
Dry Run
Complexity Analysis
| Approach |
Time |
Extra Space |
Allocations |
Meets constraint? |
| 1. Brute Force (separate array) |
O(n) |
O(n) |
Full output container |
❌ No |
| 2. Better (in-place + to_string) |
O(n) |
O(1)* |
One small string per multi-digit group |
⚠️ Borderline |
| 3. Optimal (manual digits) |
O(n) |
O(1) |
None |
✅ Yes |
Interview Notes
How to talk through it
- Point out the constant-space requirement yourself before writing any code — it immediately rules out Approach 1 and signals that this is a two-pointer, in-place problem.
- It's fine to write
to_string first to get a working in-place solution quickly, but proactively flag that it technically allocates memory, and offer the manual digit-extraction version as the fully compliant follow-up. - Explain the reverse-after-writing trick clearly — it's the one non-obvious step in the optimal solution, and walking through it confidently is what separates a strong answer here.
Common follow-ups
- "Is `to_string` really a problem here?" → acknowledge it's asymptotically negligible but still a heap allocation, and that the strict reading of "constant extra space" usually means avoiding it entirely.
- "Can the write pointer ever overtake the read pointer and corrupt unread data?" → no — the compressed form of any run is never longer than the run itself, so
write never passes read. - "What's the largest a count could be here?" → bounded by the array's length, so digit counts stay small and the digit-extraction loop runs at most a few times per group.
Edge cases to mention
- A single character, or an array where every run has length exactly 1 → the output equals the input, unchanged.
- The entire array is one repeated character → a single group with a potentially multi-digit count.
- An empty array → return 0 immediately, no groups to scan.
Related problems
- Run-Length Encoding / Decoding
- Remove Duplicates from Sorted Array (in-place two-pointer pattern)
- Move Zeroes (another constant-space, in-place rewrite)
Frequently Asked Questions
Why can't you use an extra array or string for String Compression?
The problem explicitly requires constant extra space and asks for the result to be written back into the original array, so a separate output buffer — while correct — uses O(n) extra memory and doesn't satisfy the constraint.
Which approach should I lead with in an interview?
Frame the constant-space requirement upfront, write the two-pointer in-place version (with to_string if that's fastest to get working), then offer the manual digit-extraction version as the fully compliant final answer.
How do you handle counts with more than one digit, like 12?
Write the digits least-significant-first using count % 10 and count / 10, then reverse just that written segment in place so the digits appear in the correct order afterward.
Is using std::to_string() considered O(1) extra space?
Only loosely — it allocates a small temporary string on the heap, so while it's asymptotically negligible, it isn't strictly constant extra space, which is why the fully optimal solution avoids it in favor of manual digit extraction.