题目重点:以 aabcaaaade→aabc4xade 为例,按最大连续段比较实际编码长度,处理位数变化与解码歧义。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 按连续同字符的最大 run 独立编码,token 为十进制次数、字母 x、原字符。
- 本文按 Unicode code point 衡量长度;若目标是UTF-8字节大小或用户感知字符,需要更换比较和分段单位。
- 长度相等保留原 run,不递归压缩新输出。输入允许任意字符;本文不承诺这种格式可逆。
解题路线
- 先扫描到同字符 run 的结尾,令次数 c。候选长度为 digits(c)+2,原长度为 c;只有候选严格更短才输出 token。两字符/三字符不因重复就一定压缩。
- 每个 run 的选择互不依赖,输出是各段拼接,目标长度可加;局部选更短表示就得到当前两种表示选项下的全局最短长度。这个证明不适用于词典压缩或跨run编码。
- 不要写死“重复至少4次”代替长度比较。该阈值适用于单code point和本token格式的小计数,若编码协议、count进制或字节长度变化,条件必须随合同变化。
- 扫描时原文本下标只向前推进,每个字符参加一次run识别。用输出列表最后join,避免某些语言重复拼接字符串引入二次复制。超长run通过计数表示,不需要另复制原段再计算长度。
- 解码必须单独讨论:字符串 4xa 可以是原文四个字符,也可以是四个a的token;若允许数字和x,这个表示没有无歧义逆函数。要可逆可给literal段和run段加tag/长度,或定义转义,不能靠猜测还原。
- 验收应覆盖空串、交替串、恰好相等的3次、4次、9→10和99→100的次数位数变化、数字字符和Unicode。独立验证用分组后的两种候选长度比较,而不是复刻扫描器。
手动走一遍
aabcaaaade分为 aa/b/c/aaaa/d/e。aa的token 2xa长3,比原run长2,保留;aaaa用4xa长3,输出 aabc4xade。aaa与3xa同长,必须保留aaa。原文4xa没有重复run也会保持4xa,因此解码结果无法只由输出唯一确定。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Encode each Unicode code-point run as count + x + char only when shorter.
This format is not a general reversible codec when input contains digits/x.
"""
def compress_runs(text):
output = []
i = 0
while i < len(text):
j = i + 1
while j < len(text) and text[j] == text[i]:
j += 1
count = j - i
token = str(count) + "x" + text[i]
output.append(token if len(token) < count else text[i:j])
i = j
return "".join(output)
复杂度
时间 O(n),输出长度≤n;额外空间 O(n) 包括输出。若流式逐run输出,工作状态可为一个字符和计数,但已输出结果仍由接收方持有。
易错点与追问
容易失分的边界
- 相等长度也压缩违反严格变短规则。
- 把不相邻的同字符合并。
- 将UTF-8字节与code point混算。
- 未加协议就声称encode/decode可逆。
继续深挖
- 如果必须可逆,怎样设计tag与转义?
- 输入流分块时跨块run怎么保留?
- 若按UTF-8字节计算,阈值如何变化?
- 是否允许全局选择只在整串更短时输出压缩格式?
