本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
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.
You must write an algorithm that uses only constant 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.
题意:给你一个字符数组 chars
,用算法压缩,从一个空字符串 s
开始。对于 chars
中的每组 连续重复字符 :
- 如果这一组长度为
1
,则将字符追加到s
中。 - 否则,需要向
s
追加字符,后跟这一组的长度。
压缩后得到的字符串 s
不应该直接返回 ,需要转储到字符数组 chars
中。需要注意的是,如果组长度为 10
或 10
以上,则在 chars
数组中会被拆分为多个字符。
请在 修改完输入数组后 ,返回该数组的新长度。必须设计并实现一个只使用常量额外空间的算法来解决此问题。
解法 双指针
这道题其实很简单,就是实现长程压缩算法:
- 用两个指针
i, j
,i, j
一开始指向连续相同字符chars[i]
的起点,同时用ans = 0
记录压缩后数组的长度; j
不断往后遍历,直到遇到不同字符char[j]
或到了chars[]
的末尾,此时这一组字符的长度len = j - i
;- 将字符
chars[i]
写入chars[ans++]
中; - 长度大于
1
时,先用l
记录此时的位置ans
,再顺序从个位到最高位将len
写入chars[ans++]
,然后将[l, ans - 1]
区间的数字逆序; - 最后
i = j
,继续下一轮循环。
这无疑是最自然的想法,时间复杂度为 O ( n ) O(n) O(n) ,空间复杂度为 O ( 1 ) O(1) O(1) 。代码实现如下:
class Solution {
public:
int compress(vector<char>& chars) {
int ans = 0;
for (int i = 0, n = chars.size(); i < n; ) {
int j = i;
while (j < n && chars[j] == chars[i]) ++j;
int len = j - i;
chars[ans++] = chars[i];
if (len > 1) {
int l = ans;
while (len) {
chars[ans++] = '0' + len % 10;;
len /= 10;
}
int r = ans - 1;
while (l < r) swap(chars[l++], chars[r--]);
}
i = j;
}
return ans;
}
};
//执行用时:4 ms, 在所有 C++ 提交中击败了87.32% 的用户
//内存消耗:8.6 MB, 在所有 C++ 提交中击败了71.52% 的用户
上述算法在具体细节上还能稍微简化一下,我们实际上不需要指向连续相同字符起点的指针,下面的过程相当于把上述的 j
换成了 i
:
- 先用指针
i
指向连续相同字符chars[i]
的起点,同时用ans = 0
记录压缩后数组的长度; i
不断往后遍历,直到遇到不同字符char[i + 1]
或到了chars[]
的末尾,同时用len
计数这一组字符的长度;- 将字符
chars[i]
写入chars[ans++]
中; - 长度大于
1
时,先用l
记录此时的位置ans
,再顺序从个位到最高位将len
写入chars[ans++]
,然后将[l, ans - 1]
区间的数字逆序; - 令
len = 0
,继续i
的遍历。
代码实现如下:
class Solution {
public:
int compress(vector<char>& chars) {
int ans = 0;
for (int i = 0, len = 1, n = chars.size(); i < n; ++i, ++len) {
if (i + 1 != n && chars[i] == chars[i + 1]) continue;
chars[ans++] = chars[i];
if (len > 1) {
int l = ans;
while (len) {
chars[ans++] = '0' + len % 10;
len /= 10;
}
int r = ans - 1;
while (l < r) swap(chars[l++], chars[r--]);
}
len = 0;
}
return ans;
}
};
//执行用时:0 ms, 在所有 C++ 提交中击败了100.00% 的用户
//内存消耗:8.5 MB, 在所有 C++ 提交中击败了91.39% 的用户