Problem Overview
LeetCode 443 — String Compression gives you a character array chars and asks you to compress it in-place using run-length encoding, then return the new length. The compression rule is straightforward: each group of consecutive equal characters is encoded as the character followed by its count, but only if the count is greater than one.
For example, ["a","a","b","b","c","c","c"] compresses to ["a","2","b","2","c","3"] and returns 6. A single character like "a" appearing alone becomes just "a" — no count is appended. A group of 12 identical characters like "b" becomes "b","1","2" because 12 is written as its individual digits.
The constraint that makes this interesting is the in-place requirement with O(1) extra space. You cannot build a new array; you must overwrite the input array from the front. The return value is the new length of the compressed array, not the entire array content.
- Input: char array chars with consecutive groups of equal characters
- Output: new length after in-place run-length encoding
- ["a","a","b","b","c","c","c"] → ["a","2","b","2","c","3"], return 6
- Single characters have no count appended: "a" stays as "a" not "a1"
- Multi-digit counts write each digit separately: count 12 writes "1" then "2"
- Must be in-place with O(1) extra space; return value is the new array length
Two-Pointer Strategy
The key insight is using two pointers with distinct roles. The read pointer scans forward through the original array identifying runs of consecutive equal characters. The write pointer tracks where the next compressed character or digit should be placed in the array.
The write pointer always stays at or behind the read pointer, which guarantees you never overwrite characters you have not yet read. This is the critical invariant that makes in-place modification safe: by the time the write pointer reaches a position, the read pointer has already moved past it and extracted all the information needed.
The algorithm loops until the read pointer reaches the end of the array. For each iteration, it identifies the current character, counts how many consecutive times it appears, writes the character at the write position, then writes each digit of the count if the count exceeds one. Both pointers advance through the array in a single left-to-right pass, giving O(n) time complexity.
Read Pointer Counts, Write Pointer Places
Use a read pointer to count runs and a write pointer to place results. The write pointer always stays behind or at the read pointer so you never overwrite unread data. This invariant holds because even in the worst case (all unique characters), each character takes at least one write slot, and the read pointer advances at least one step per iteration — so the write pointer can never catch up.
Counting Runs
At the start of each iteration, the read pointer sits at the beginning of a new run. Record the current character as chars[read]. Then advance the read pointer forward as long as the next character equals the current character and the read pointer has not reached the end of the array.
When the inner while loop exits, the count of consecutive equal characters is read minus the starting position of the run (start). This gives you the run length without any extra data structure — just arithmetic on the two pointer values. For example if a run of "b" starts at index 3 and the read pointer ends at index 8, the count is 5.
The outer while loop condition is simply read < n where n is the total length of the array. Each iteration of the outer loop handles exactly one complete run, so the outer loop executes at most n times total across the entire algorithm. The inner loop work is also bounded by n across all iterations, giving the overall O(n) time guarantee.
- 1Set start = read (mark the beginning of the current run)
- 2Record current char: c = chars[read]
- 3Inner loop: while read < n and chars[read] == c, increment read
- 4After inner loop: count = read - start (the run length)
- 5Write c to chars[write], then increment write
- 6If count > 1, convert count to digit characters and write each one
Writing the Count
After writing the character itself, check if the count is greater than one. If so, convert the count to its string representation and write each character of that string into the array at successive write positions. Count 1 writes nothing — the character alone represents a run of one. Count 2 writes "2". Count 12 writes "1" then "2".
In Python, converting the count to digits is simply str(count), and you can iterate over the string directly: for digit in str(count): chars[write] = digit; write += 1. In Java, use String.valueOf(count) to get the digit string, then iterate character by character writing each to chars[write++].
The write pointer advances once for each digit written. For single-digit counts (1-9) this is one step. For two-digit counts (10-99) this is two steps. The maximum count is n (all characters equal), which is at most 10^4 in the problem constraints — a five-digit number at most, so the write pointer advances at most 5 extra steps per run for count digits.
Converting Count to Digits — The Trickiest Part
Converting count to digits is the trickiest part: use str(count) in Python or Integer.toString(count) in Java to get the digit string, then write each character individually. Do NOT write the integer directly — the array holds string characters, not integers. Count 12 must become the two characters "1" and "2", not the single character representing the number twelve.
Walk-Through Example
Trace ["a","b","b","b","b","b","b","b","b","b","b","b","b"] through the algorithm. The array has one "a" followed by twelve "b" characters. Both read and write start at index 0.
Iteration 1: start=0, c="a", read advances to 1 (next char is "b", not "a"). Count = 1-0 = 1. Write "a" at write=0, advance write to 1. Count is 1 so no digit is written.
Iteration 2: start=1, c="b", read advances through all twelve "b" characters to index 13. Count = 13-1 = 12. Write "b" at write=1, advance write to 2. Count 12 > 1: write "1" at write=2, write "2" at write=3, advance write to 4. The result is ["a","b","1","2",...] and the function returns write = 4.
- 1Input: ["a","b","b","b","b","b","b","b","b","b","b","b","b"] (1 "a" + 12 "b")
- 2read=0, write=0: c="a", run length=1, write "a", write advances to 1
- 3read=1, write=1: c="b", inner loop advances read to 13, count=12
- 4Write "b" at index 1, write advances to 2
- 5Count 12 > 1: write "1" at index 2, write "2" at index 3, write advances to 4
- 6read=13 equals n=13, outer loop exits
- 7Return write=4; compressed result is ["a","b","1","2"]
Code Walkthrough: Python and Java
Python solution: def compress(chars): read = write = 0; n = len(chars); while read < n: start = read; c = chars[read]; while read < n and chars[read] == c: read += 1; chars[write] = c; write += 1; count = read - start; if count > 1: for d in str(count): chars[write] = d; write += 1; return write. Two nested while loops, O(n) time, O(1) extra space.
Java solution: public int compress(char[] chars) { int read = 0, write = 0, n = chars.length; while (read < n) { char c = chars[read]; int start = read; while (read < n && chars[read] == c) read++; chars[write++] = c; int count = read - start; if (count > 1) { for (char d : String.valueOf(count).toCharArray()) chars[write++] = d; } } return write; }. The structure mirrors Python with the same two-pointer invariant.
Both solutions return the write pointer value as the new length. The time complexity is O(n) because each character is read once and written at most a constant number of times. The space complexity is O(1) because no extra arrays are allocated — only a handful of integer variables for the pointers and count.
Don't Write Count for Runs of 1
Do not write the count for runs of length 1: the problem explicitly states that single characters should NOT have a number appended. Writing "a1" instead of "a" for a single character is wrong and will cause incorrect output and wrong return length. Guard every count write with if count > 1 (Python) or if (count > 1) (Java) before the digit-writing loop.