目录

题目描述

面试题 01.06. 字符串压缩

题意分析

输入是一个只含大小写字母的字符串,要把每一段连续相同的字符写成「字符 + 该段长度」的形式,例如 aabcccccaaa 变成 a2b1c5a3

关键限定有两个:一是压缩的对象是连续段,不是字符在全串中的总出现次数,散落在不同位置的相同字符不能合并;二是压缩完还要比长度,只有压缩串严格更短才返回它,否则原样返回输入。

约束信号:字符集只有字母,不用担心数字与字符混淆;长度上限五万,说明要一次线性扫描完成,并且拼接结果时不能用会产生大量中间对象的写法。

边界包括:空串与长度为 1 的串(压缩后必然不会更短);全串同一个字符(压缩收益最大);相邻字符全不相同(压缩后长度翻倍);某段长度达到两位数甚至五位数;最后一段直到扫描结束才闭合。

解法:连续段计数模拟

核心思路

压缩单位是连续相同字符形成的段,不能用哈希表统计全局频次。例如 aabaa 有三段,应得到 a2b1a2,而不是 a4b1

用左右指针扫描每一段:left 指向段首,right 找到第一个不同字符,于是当前字符是 S[left],段长是 right - left。写入后令 left = right,继续处理下一段。

循环不变量是:每轮开始时,[0, left) 已被完整压缩,left 正好指向下一段段首;因此每个字符只会被扫描一次。压缩结果只会继续变长,一旦其长度达到原串长度,就可直接返回原串;否则全部处理完后,仍要用“严格更短”判断是否采用压缩结果。

解题步骤

  1. 初始化 left = 0 和结果缓冲区。
  2. left + 1 开始移动 right,直到越界或字符变化。
  3. 追加 S[left] 与段长 right - left
  4. 若当前结果长度已不小于原串,直接返回原串;否则令 left = right
  5. 所有段处理完后返回压缩串。

aabcccccaaa 为例,依次识别出 aa | b | ccccc | aaa,写成 a2b1c5a3,长度 8 小于原串长度 11,因此返回压缩串。对于 aabb,写到 a2b2 时长度等于原串,必须返回 aabb

代码实现

class Solution {
    public String compressString(String S) {
        int n = S.length();
        StringBuilder compressed = new StringBuilder();

        for (int left = 0; left < n; ) {
            int right = left + 1;
            while (right < n && S.charAt(right) == S.charAt(left)) {
                right++;
            }
            compressed.append(S.charAt(left)).append(right - left);
            if (compressed.length() >= n) {
                return S;
            }
            left = right;
        }
        return compressed.toString();
    }
}
import "strconv"

func compressString(S string) string {
    compressed := make([]byte, 0, len(S))

    for left := 0; left < len(S); {
        right := left + 1
        for right < len(S) && S[right] == S[left] {
            right++
        }
        compressed = append(compressed, S[left])
        compressed = strconv.AppendInt(compressed, int64(right-left), 10)
        if len(compressed) >= len(S) {
            return S
        }
        left = right
    }
    return string(compressed)
}

复杂度分析

  • 时间复杂度:$O(n)$。左右指针只向前移动,每个字符至多被检查一次。
  • 空间复杂度:$O(n)$。最坏情况下结果缓冲区与原串同阶;提前判定不改变渐进复杂度。

关键点总结

  • 统计的是连续段,不是字符在整串中的总频次。
  • [left, right) 精确表示当前段,段长直接是 right - left,无需单独处理最后一段。
  • 只有压缩串严格更短时才能返回;等长也返回原串。
  • 面试时应说明为何使用可变缓冲区:循环内直接拼接不可变字符串可能退化为 $O(n^2)$。

易错点总结

  • 把非相邻的相同字符合并:aabaa 不能写成 a4b1
  • 漏掉严格比较:aabb -> a2b2 等长,答案仍应是 aabb
  • 计数只支持一位数字:连续 12 个 a 应写成 a12,不能把 12 强转成单个字符。
  • 段结束后没有令 left = right,会重复扫描或陷入死循环。
  • Go 代码按字节扫描依赖题目“只含英文字母”的约束;若输入可能含多字节字符,应改为按 rune 处理。

相似题目

题目 难度 考察点
443. 压缩字符串 中等 原地压缩与双指针回填
1446. 连续字符 简单 最长连续段长度
38. 外观数列 中等 逐轮递推生成描述串
482. 密钥格式化 简单 定长分组重排字符串
58. 最后一个单词的长度 简单 从末尾定位一段字符