暴力法时间复杂度O(n³)不可用于线上,推荐滚动哈希+二分答案:固定长度L遍历子串,用双哈希+unordered_set去重并验证,预处理过滤控制字符,返回string_view避免拷贝与悬空。

暴力法能跑通但千万别在线上用
直接两层循环枚举所有子串,再用哈希表统计出现次数,时间复杂度是 O(n³)——对长度 10⁴ 的字符串,操作次数轻松破百亿。实际测试中,std::string 的 substr() 调用本身就有开销,加上每次构造新字符串、哈希计算,不到 500 字符就明显卡顿。
真正可用的起点是后缀数组(SA)或后缀自动机(SAM),但它们实现复杂、调试成本高。如果只是临时分析日志或做离线数据探查,更推荐先试试滚动哈希 + 二分答案的组合方案,它平衡了可读性和性能。
用 std::unordered_set + 滚动哈希做初步筛选
核心思路:固定子串长度 L,遍历所有长度为 L 的子串,用滚动哈希快速计算哈希值并存入 std::unordered_set;发现重复哈希值时,再用原始字符串比对确认是否真重复(防哈希冲突)。然后对 L 二分搜索,找最大可行长度。
-
L的取值范围是[1, s.length()/2],因为重复子串至少出现两次,总长至少是2*L - 推荐用双哈希(两个不同 base 和 mod)降低冲突概率,单哈希在 10⁵ 级别字符串中已观察到误判
- 注意
std::unordered_set插入失败返回的pair<bool iterator></bool>,要用.first判断是否已存在 - 避免在循环内反复调用
s.substr(i, L),改用s.data() + i配合自定义哈希函数处理原始字符指针
遇到空格/换行等不可见字符时结果会错
很多真实场景下的长字符串(比如 HTTP 响应体、JSON 日志)包含 \n、\r、\t,而默认的哈希函数可能把它们和空格等价处理,或者因编码差异(如 UTF-8 多字节)导致切片错位。这不是算法问题,是预处理缺失。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 务必先统一清理:用
std::remove_if过滤掉控制字符,或明确约定只在isprint(c) && c != ' '的字符范围内查找 - 若需保留空白字符,必须确保哈希函数对
\n和\r返回不同值——检查你用的std::hash<:string_view></:string_view>是否满足,否则手动实现 - Windows 行尾
\r\n在某些编译器下会被std::getline自动剥离,但原始内存里还留着,导致同一文件在不同平台读取后哈希不一致
最长重复子串可能有多个,std::string_view 是唯一安全返回方式
返回 std::string 会触发拷贝,而最长重复子串往往很长;返回裸指针又容易悬空。正确做法是只返回 std::string_view,它不拥有数据,仅引用原字符串。
- 函数签名应该类似:
std::optional<:string_view> longest_duplicate_substring(const std::string& s)</:string_view> - 调用方必须保证原
std::string的生命周期长于std::string_view的使用周期,否则访问就是未定义行为 - 不能直接对
std::string_view调用.data()后传给 C 接口,除非确认其底层存储连续且以\0结尾——std::string_view不保证这点
边界情况比想象中多:空串、全相同字符、恰好两个相同长子串但位置重叠……这些都得在二分判定函数里单独处理,漏一个就返回错误结果。

















