String Compression

Medium Two Pointers

Problem

Given an array of characters chars, compress it in-place such that consecutive groups of the same character are replaced by the character followed by the group's length, and return the new length of the array after compression.

  • 1 ≤ chars.length ≤ 2000
  • chars[i] is a lowercase English letter, uppercase English letter, digit, or symbol.

Example

Input: chars = ["a","a","b","b","c","c","c"]
Output: 6

The groups are "aa", "bb", and "ccc". After compression, the array becomes ["a","2","b","2","c","3"]. The new length is 6. The algorithm scans the array, counts consecutive characters, writes the character once, and if the count is greater than 1, writes the count digits sequentially. This in-place approach avoids extra space by using two pointers: one to read and one to write.

Approach

Straightforward Solution

A naive approach would build a new string or array to store the compressed result, which uses extra space and violates the in-place constraint. Another brute-force approach might repeatedly shift elements, resulting in O(n²) time complexity.

Core Observation

Compression depends on identifying consecutive groups of identical characters and replacing them with the character plus the count if greater than one. The key is to perform this in-place, which requires careful management of read and write pointers to avoid overwriting unprocessed data.

Path to Optimal

The insight is to use two pointers: one to iterate through the input array (read pointer) and one to track the position to write compressed characters (write pointer). For each group of identical characters, count its length, write the character once, and if count > 1, write each digit of the count sequentially. This approach ensures a single pass with O(n) time and O(1) extra space.

Optimal Approach

Iterate through the array with a read pointer. For each group of identical characters, count its length by advancing the read pointer. Write the character at the write pointer, increment write. If count > 1, convert count to string and write each digit at the write pointer, incrementing write accordingly. Return the write pointer as the new length.

Time

O(n)

Each character is visited at most twice: once by the read pointer to count groups, and once by the write pointer to write compressed characters, resulting in linear time.

Space

O(1)

The algorithm uses only a fixed number of pointers and variables for counting and writing, modifying the input array in-place without additional data structures.

Pattern Spotlight

Two Pointers (In-Place Compression)

Use one pointer to read and count groups, and another to write compressed output in-place, ensuring no data loss or overwriting by carefully advancing pointers only after processing each group.

Solution

1class Solution:
2 def compress(self, chars: List[str]) -> int:
3 write = 0
4 i = 0
5
6 while i < len(chars):
7 char = chars[i]
8 count = 0
9
10 while i < len(chars) and chars[i] == char:
11 i += 1
12 count += 1
13
14 chars[write] = char
15 write += 1
16
17 if count > 1:
18 for d in str(count):
19 chars[write] = d
20 write += 1
21
22 return write

Step-by-Step Solution

1

Initialize Read and Write Pointers for Traversal and Compression

3write = 0
4i = 0

Objective

To set up two pointers: one for reading through the input array and one for writing compressed characters in-place.

Key Insight

Using two pointers allows simultaneous reading and writing without extra space. The read pointer scans the input to identify groups, while the write pointer tracks where to write compressed output. This separation prevents overwriting unprocessed characters and enables in-place modification.

Interview Quick-Check

Core Logic

The read pointer traverses the input array to identify groups, while the write pointer marks the position to write compressed characters.

State & Boundaries

Both pointers start at zero, ensuring the entire array is processed from the beginning.

Common Pitfalls & Bugs

Initializing pointers incorrectly or mixing their roles can cause data corruption or incorrect compression.

2

Traverse Input to Identify Consecutive Character Groups

6while i < len(chars):
7 char = chars[i]
8 count = 0
10 while i < len(chars) and chars[i] == char:
11 i += 1
12 count += 1

Objective

To iterate through the input array, counting the length of each group of identical characters.

Key Insight

By advancing the read pointer while the current character matches the group's character, the algorithm efficiently counts group length in a single pass. This counting is essential to determine whether to write just the character or the character plus count digits.

Interview Quick-Check

Core Logic

The inner loop increments the read pointer and count as long as characters match, identifying the full extent of each group.

State & Boundaries

The loop terminates when the read pointer reaches the array's end or encounters a different character.

Common Pitfalls & Bugs

Failing to check array bounds or incorrectly updating the read pointer can cause index errors or infinite loops.

3

Write Compressed Characters and Counts In-Place

14chars[write] = char
15write += 1
17if count > 1:
18 for d in str(count):
19 chars[write] = d
20 write += 1

Objective

To write the compressed representation of each group into the array at the write pointer position.

Key Insight

Writing the character first ensures the compressed string starts correctly. If the count is greater than one, converting the count to a string and writing each digit individually preserves multi-digit counts. Incrementing the write pointer after each write maintains the correct position for subsequent writes.

Interview Quick-Check

Core Logic

Writing the character and then the count digits (if count > 1) in-place ensures the array reflects the compressed form without extra space.

State & Boundaries

The write pointer advances exactly by the number of characters written, maintaining correct indexing.

Common Pitfalls & Bugs

Not converting count to string or writing digits individually can cause incorrect compression for counts > 9.

4

Return Length of Compressed Array

22return write

Objective

To return the final length of the compressed array after all groups have been processed.

Key Insight

The write pointer's position after processing all groups represents the length of the compressed array. Returning this value allows the caller to know the valid portion of the array.

Interview Quick-Check

Core Logic

Returning the write pointer value correctly indicates the length of the compressed array.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 10 Critical
while i < len(chars) and chars[i] == char:

Start inner loop to count consecutive occurrences of the current character.

This loop advances the read pointer and increments count while the characters match, efficiently identifying group length.

Line 14 Critical
chars[write] = char

Write the current character at the write pointer position.

Writing the character first ensures the compressed array starts with the correct character for the group.

Line 19 Critical
chars[write] = d

Write each count digit at the write pointer position.

Writing digits individually maintains the correct order and placement of the count in the compressed array.

Line 22 Critical
return write

Return the length of the compressed array.

The write pointer's final position represents the length of the compressed array, signaling the valid portion of the array after compression.

Line 3 Core
write = 0

Initialize the write pointer to track the position for writing compressed characters.

The write pointer marks where the next compressed character or count digit should be written, enabling in-place modification without overwriting unprocessed data.

Line 17 Core
if count > 1:

Check if the count is greater than one to write count digits.

Only groups with more than one character require count digits to be written, preserving compression correctness.

Line 18 Core
for d in str(count):

Iterate over each digit in the string representation of count.

Converting count to string and iterating digits handles multi-digit counts correctly, ensuring accurate compression.

Line 4 Core
i = 0

Initialize the read pointer to traverse the input array.

The read pointer scans the input array to identify groups of consecutive characters, driving the compression process.

Line 7 Core
char = chars[i]

Store the current character to identify the group.

Capturing the current character allows counting how many times it repeats consecutively, which is essential for compression.

Line 11 Core
i += 1

Advance the read pointer to the next character.

Incrementing the read pointer moves the scan forward, enabling counting of the current group without missing characters.

Line 12 Core
count += 1

Increment the count of the current character group.

Counting each occurrence is necessary to determine if the count should be written after the character.

Line 15 Core
write += 1

Advance the write pointer after writing the character.

Incrementing the write pointer prepares the position for writing count digits or the next character.

Line 20 Core
write += 1

Advance the write pointer after writing each count digit.

Incrementing the write pointer after each digit ensures subsequent digits or characters are written in the correct positions.

Line 6 Standard
while i < len(chars):

Start the outer loop to process the entire array.

This loop ensures every character in the array is visited and grouped appropriately until the end is reached.

Line 8 Standard
count = 0

Initialize the count of consecutive characters to zero.

Starting the count at zero prepares to tally the number of identical characters in the current group.

Test Your Understanding

Why must the write pointer only advance after fully processing each group of characters?

Because advancing the write pointer prematurely risks overwriting characters that have not yet been read, breaking the in-place compression logic and corrupting data.

Related Problems

Two Pointers pattern

Don't just read it. Drill it.

Reconstruct String Compression from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the String Compression drill

or drill a free problem