Compress a character array in-place using two pointers and grouped character counting.
Problem Restatement
We are given an array of characters:
charsWe must compress the array in-place using these rules:
| Case | Output |
|---|---|
| Single character | Keep the character only |
| Repeated character | Write character followed by count |
For example:
["a","a","b","b","c","c","c"]becomes:
["a","2","b","2","c","3"]The compressed result must be written back into the same array.
The function returns the new length after compression.
Counts larger than 9 must be split into multiple characters.
For example:
12must be written as:
"1", "2"The official problem asks for in-place compression with constant extra space. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | Character array chars |
| Output | Length of compressed array |
| Compression | Character + frequency |
| Single occurrence | No count written |
| Extra space | Must be constant |
Example function shape:
def compress(chars: list[str]) -> int:
...Examples
Example 1:
chars = ["a","a","b","b","c","c","c"]Compression groups:
| Group | Result |
|---|---|
"aa" | "a2" |
"bb" | "b2" |
"ccc" | "c3" |
Compressed array:
["a","2","b","2","c","3"]Returned length:
6Example 2:
chars = ["a"]Single characters keep only themselves.
Compressed array:
["a"]Returned length:
1Example 3:
chars = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]There are:
12copies of "b".
Compressed array becomes:
["a","b","1","2"]Returned length:
4First Thought: Build a New String
We could create another array:
result = []Then append compressed groups into it.
This is simple, but the problem requires in-place modification with constant extra space.
So we must overwrite the original array directly.
Key Insight
Compression happens group by group.
For example:
a a a b b ccontains groups:
aaa
bb
cWe can process each group independently.
Use two pointers:
| Pointer | Meaning |
|---|---|
i | Start of current group |
j | Finds the end of the group |
We also keep a write pointer:
writewhich tells where to place compressed output.
For each group:
- Write the character.
- Compute the group size.
- If size > 1, write the digits of the count.
Algorithm
Initialize:
write = 0
i = 0While i < len(chars):
- Find the maximal group starting at
i. - Let
jbe the first index after the group. - Group size:
count = j - i- Write the character at position
write. - Increase
write. - If
count > 1:- Convert count to string.
- Write each digit into the array.
- Move:
i = jAt the end, return write.
Correctness
At every iteration, the algorithm processes exactly one maximal contiguous group of equal characters.
Suppose the current group contains:
countcopies of character c.
The compression rules require:
| Condition | Written output |
|---|---|
count == 1 | "c" |
count > 1 | "c" followed by decimal digits of count |
The algorithm writes exactly this representation.
The write pointer always points to the next unused position in the compressed prefix of the array. Therefore, writing the compressed representation overwrites old data safely without losing unprocessed characters, because the read pointer i and scanning pointer j always stay ahead.
After processing a group, the algorithm advances i to the first unprocessed character.
Thus every group is compressed exactly once, and the final prefix:
chars[:write]contains the required compressed array.
Therefore, returning write gives the correct compressed length.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Every character is scanned once |
| Space | O(1) | Compression is written in-place |
Implementation
class Solution:
def compress(self, chars: list[str]) -> int:
write = 0
i = 0
n = len(chars)
while i < n:
j = i
while j < n and chars[j] == chars[i]:
j += 1
count = j - i
chars[write] = chars[i]
write += 1
if count > 1:
for digit in str(count):
chars[write] = digit
write += 1
i = j
return writeCode Explanation
We use:
write = 0to track where compressed output should be written.
The pointer:
i = 0marks the start of the current group.
For every group, we extend:
jwhile characters remain equal:
while j < n and chars[j] == chars[i]:Then:
count = j - iis the group size.
We always write the group character:
chars[write] = chars[i]
write += 1If the group size exceeds one:
if count > 1:we write every digit of the count:
for digit in str(count):This handles counts larger than 9.
Finally, we move to the next group:
i = jAt the end, write equals the compressed length.
Testing
def compressed(chars):
s = Solution()
length = s.compress(chars)
return chars[:length]
def run_tests():
assert compressed(
["a","a","b","b","c","c","c"]
) == ["a","2","b","2","c","3"]
assert compressed(
["a"]
) == ["a"]
assert compressed(
["a","b","b","b","b","b","b","b","b","b","b","b","b"]
) == ["a","b","1","2"]
assert compressed(
["a","a","a","a"]
) == ["a","4"]
assert compressed(
["a","b","c"]
) == ["a","b","c"]
assert compressed(
["z"] * 15
) == ["z","1","5"]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Standard example | Checks multiple groups |
| Single character | Checks no count writing |
Count 12 | Checks multi-digit counts |
| All same character | Checks one large group |
| No repeated characters | Checks unchanged compression |
Count 15 | Checks larger multi-digit values |