String Compression

LeetCode problem 443. String Compression.

## Problem description

Given an array of characters `chars`, compress it using the following algorithm:

Begin with an empty string `s`. For each group of **consecutive repeating characters** in `chars`:

* If the group's length is 1, append the character to `s`.
* Otherwise, append the character followed by the group's length.

The compressed string `s` **should not be returned separately**, but instead be stored **in the input character array `chars`**. Note that group lengths that are 10 or longer will be split into multiple characters in `chars`.

After you are done **modifying the input array**, return *the new length of the array*.

Follow up
Could you solve it using only `O(1)` extra space?

Example 1
**Input:** `chars = ["a","a","b","b","c","c","c"]`
**Output:** Return 6, and the first 6 characters of the input array should be: `["a","2","b","2","c","3"]`.
**Explanation:** The groups are "`aa`", "`bb`", and "`ccc`". This compresses to "`a2b2c3`".
Example 2
**Input:** `chars = ["a"]`
**Output:** Return 1, and the first character of the input array should be: `["a"]`.
**Explanation:** The only group is "`a`", which remains uncompressed since it's a single character.
Example 3
**Input:** `chars = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]`
**Output:** Return 4, and the first 4 characters of the input array should be: `["a","b","1","2"]`.
**Explanation:** The groups are "`a`" and "`bbbbbbbbbbbb`". This compresses to "`ab12`".
Example 4
**Input:** `chars = ["a","a","a","b","b","a","a"]`
**Output:** Return 6, and the first 6 characters of the input array should be: `["a","3","b","2","a","2"]`.
**Explanation:** The groups are "`aaa`", "`bb`", and "`aa`". This compresses to "`a3b2a2`". Note that each group is independent even if two groups have the same character.

Constraints
* `1 <= chars.length <= 2000`
* `chars[i]` is a lower-case English letter, upper-case English letter, digit, or symbol.

## Analysis

### Corner case

If the input array's length is 0 or 1, then we just return the length, and do nothing on the array.

### General approach

There are some important properties we would notice as we modify the input character array in-place:

1. Repeat counts of 1 are not stored in the output.
2. The number characters taken to store a letter followed repeat counts in the output is less than or equal to the number of characters in the original segment of repetition.
	* 2-9: one original character + one digit.
	* 10-99: one original character + two digits.
	* 100-999: one original character + three digits.
	* …
3. Modifying the input array in-place never expands it.

So we can use three pointers as follows:

1. **`slow` pointer**: to mark the beginning of a segment of repeated characters.
2. **`fast` pointer**: to move forward to find the next different character / end of input.
3. **`compressTail` pointer**: to mark the tail of generated compressed string, and to return the length of compressed string at the end.

We start the loop by setting `slow = 0`, `fast = 1`, and `compressTail = 0`.

Then we move forward the fast pointer until it passes the end.

In each single iteration we do the following:

* If `fast` has not reached one-past-end, and `chars[slow] == chars[fast]`, then we just move forward `fast` without changing the input array.
* If `fast` has reached one-past-end, or if `chars[slow] != chars[fast]`, then we need to do the compression:
	1. `repeatCount = fast - slow`.
	2. Copy `chars[slow]` to `chars[compressTail]`.
	3. Move forward `compressTail`.
	4. If `repeatCount > 1`, then put each digit of `repeatCount` into `chars`, starting from `compressTail` position. Move `compressTail` forward, one past the last digit.
	5. Set `slow = fast`.
	6. Move forward `fast` by one position.

Repeat until `fast` reaches two-past-end, i.e. the entire string is compressed.

#### Illustration

Color notations on Input row
* lime or cyan — characters that have been processed, and become part of output.
* silver — characters that have been processed, and do not become part of output.
* uncolored — characters yet to be processed.

Input: `abbcccccccccccdeeeff`

 `compressTail` | 0

After processing `a`

 `compressTail` |  | 1

After processing `b`

After processing `c`

After processing `d`

After processing `e`

Output
* Return value: 9
* Compressed array: `["a", "b", "2", "c", "1", "1", "d", "e", "2"]`

### Complexity analysis

Time complexity
Since we processed the entire string in one-pass, character by character, and for each character we have constant amount of computation, the time complexity is **O(n)**
Space complexity
Since we only used three pointers to complete the traversal and used no additional data structures, the space complexity is **O(1)**.
## Solution

### C++

```
class Solution {
private:
 // put repeat count into original char vector, in-place
 // return the index right after the last character being modified
 static int putRepeatCount(std::vector<char>& chars, int repeatCount, int startPos) {
 // get all characters for size
 std::deque<char> repeatCountChars;
 while (repeatCount != 0) {
 repeatCountChars.push\_front('0' + repeatCount % 10);
 repeatCount /= 10;
 }

 // put repeat count in-place
 int i = startPos;
 while (!repeatCountChars.empty()) {
 // in this problem it is GUARANTEED that
 // compressed string will not be longer than original string
 // so no need to expand chars vector
 chars[i++] = repeatCountChars.front();
 repeatCountChars.pop\_front();
 }
 return i;
 }

public:
 int compress(std::vector<char>& chars) {
 const int len = chars.size();

 // corner case
 if (len <= 1) {
 return len;
 }

 int slow = 0, fast = 1, compressTail = 0;
 while (fast <= len) {
 // move forward fast pointer if slow and fast are same
 if ((fast < len) && (chars[slow] == chars[fast])) {
 ++fast;
 }
 else {
 // process compression
 int repeatCount = fast - slow;
 chars[compressTail++] = chars[slow];
 if (repeatCount > 1) {
 compressTail = putRepeatCount(chars, repeatCount, compressTail);
 }
 slow = fast++;
 }
 }

 return compressTail;
 }
};

```

### Java

```
class Solution {
 // put repeat count into original char vector, in-place
 // return the index right after the last character being modified
 private static int putRepeatCount(char[] chars, int repeatCount, int startPos) {
 // get all characters for size
 Deque<Character> repeatCountChars = new ArrayDeque<>();
 while (repeatCount != 0) {
 repeatCountChars.offerFirst((char)('0' + repeatCount % 10));
 repeatCount /= 10;
 }

 // put repeat count in-place
 int i = startPos;
 while (!repeatCountChars.isEmpty()) {
 // in this problem it is GUARANTEED that
 // compressed string will not be longer than original string
 // so no need to expand chars vector
 chars[i++] = repeatCountChars.pollFirst();
 }
 return i;
 }

 public int compress(char[] chars) {
 final int len = chars.length;

 // corner case
 if (len <= 1) {
 return len;
 }

 int slow = 0, fast = 1, compressTail = 0;
 while (fast <= len) {
 // move forward fast pointer if slow and fast are same
 if ((fast < len) && (chars[slow] == chars[fast])) {
 ++fast;
 }
 else {
 // process compression
 int repeatCount = fast - slow;
 chars[compressTail++] = chars[slow];
 if (repeatCount > 1) {
 compressTail = putRepeatCount(chars, repeatCount, compressTail);
 }
 slow = fast++;
 }
 }

 return compressTail;
 }
}

```