基于滚动哈希的高效字符串压缩算法(支持重复模式检测与编码)
字数 2208 2025-12-16 09:27:28
基于滚动哈希的高效字符串压缩算法(支持重复模式检测与编码)
题目描述
设计一种基于滚动哈希的字符串压缩算法,能够检测字符串中的重复模式(例如连续重复子串、间隔重复模式等),并将重复部分编码为更短的表示形式(例如[起始位置,长度]或[模式,重复次数])。算法需支持快速扫描和匹配,适用于长字符串的压缩场景。
解题过程循序渐进讲解
步骤1:理解核心需求
- 压缩目标:将原始字符串转换为更短的格式,通过识别重复出现的子串并用指针(位置+长度)或计数标记代替。
- 关键挑战:如何在O(n)或接近O(n)时间内找出所有重复子串?如果暴力比较所有子串,复杂度为O(n²),不可接受。
- 滚动哈希的作用:将任意长度子串映射为固定长度的哈希值,使得比较子串内容时只需比较哈希值,从而加速重复检测。
步骤2:滚动哈希的设计
滚动哈希(Rabin-Karp哈希)常用于在O(1)时间内计算相邻子串的哈希值,特别适用于滑动窗口场景。
- 哈希函数选择:常用多项式哈希,例如对字符串s,其子串s[i:j]的哈希为:
\(hash = (s[i] \cdot p^{j-i-1} + s[i+1] \cdot p^{j-i-2} + ... + s[j-1]) \mod M\)
其中p为质数基数(如31、131),M为较大质数(如1e9+7)。 - 滚动计算:已知s[i:j]的哈希,则s[i+1:j+1]的哈希可通过减去s[i]的贡献、加上s[j]的贡献,并乘以p的幂来快速更新。
- 预计算幂:提前计算p⁰, p¹, …, pⁿ mod M,避免重复计算。
步骤3:重复模式检测策略
检测分为两类:
- 相邻重复:如
"abcabcabc"中"abc"连续重复3次。- 方法:固定窗口长度L,计算每个起始位置i的哈希,比较连续窗口的哈希值,若相同则计数重复次数。
- 非相邻重复:如
"xyz123...xyz",相同子串出现在不同位置。- 方法:对每个可能长度L,计算所有起始位置i的哈希,用哈希表记录
{哈希值: [起始位置列表]}。若同一哈希对应多个位置,则这些位置开始的长度为L的子串可能相同(需进一步验证防止哈希冲突)。
- 方法:对每个可能长度L,计算所有起始位置i的哈希,用哈希表记录
步骤4:避免哈希冲突
滚动哈希可能产生不同字符串映射到相同哈希值的情况(冲突)。需在哈希匹配后进行逐字符验证,确保子串真正相同。但冲突概率较低,验证操作仅在哈希匹配时进行,不影响整体效率。
步骤5:压缩编码方案
检测到重复子串后,选择一种编码格式:
- 指针表示:
(偏移, 长度),例如重复子串用[位置, 长度]表示,位置是相对于已编码文本的偏移。 - 计数表示:若为连续重复(如
"aaabbb"),可编码为[字符, 重复次数],但本题目更关注通用重复模式,因此常用指针表示。 - 编码示例:字符串
"ababcdcdababcdcd"中"abcd"重复出现,可表示为原始字符+指针"ab[2,4]cd[0,4]"(需设计具体编码格式)。
步骤6:算法步骤分解
假设实现压缩函数compress(s):
- 初始化:结果数组
result存储压缩后的片段(字符或指针),encoded字符串存储已编码的原始文本用于偏移计算。 - 遍历所有可能长度L(从1到n/2):
- 计算每个起始位置i、长度为L的子串的滚动哈希。
- 使用哈希表
map记录每个哈希值的最新出现位置(或所有位置列表)。 - 当发现当前哈希值在
map中存在,且当前起始位置i与之前位置prev的差值≥L(确保不重叠或根据策略允许重叠),则找到重复。
- 选择最优重复:对每个位置i,检查从i开始的最长重复(通过扩展L或比较哈希序列)。
- 编码重复段:若从i开始存在长度为L的重复,且重复次数≥2,则将重复段编码为指针
[prev, L],并将i向后移动L×重复次数。 - 非重复字符:若当前位置无重复,则直接输出字符,并移动一位。
- 生成最终压缩结果:将全部片段拼接为字符串或字节序列。
步骤7:举例说明
以字符串"abcababc"为例:
- 扫描到L=3时,发现子串
"abc"的哈希在位置0和3相同,进一步验证内容确实相同,且不重叠。 - 从位置0开始的
"abc"与位置3的"abc"重复,但中间有"ab",因此可编码为:原始"abcab"+指针[0,3]? 这并不最优,因为第二个"abc"与第一个完全匹配。 - 更优方式:将整个串视为
"abc"重复两次,但中间有"ab"打断,所以需分段:"abcab"作为文字,后面"abc"用指针[0,3]表示,最终为"abcab[0,3]"。
步骤8:时间复杂度优化
- 滚动哈希计算所有子串需O(n²)?可通过限制最大重复长度(如256)将复杂度降为O(n×maxL)。
- 使用后缀数组或后缀自动机可在O(n)内找到所有重复子串,但实现复杂。滚动哈希在平均情况下接近O(n)(尤其限制L时)。
步骤9:解压缩
解压缩时,读取压缩数据,遇到原始字符直接输出,遇到指针[偏移,长度]则从已解压部分拷贝指定长度的子串。
总结
本题结合滚动哈希快速匹配、哈希表记录子串出现位置、贪心选择最长重复,实现基于字典的压缩算法(类似LZ77)。关键点在于滚动哈希的O(1)更新、冲突验证和指针编码设计。