当前位置: 首页 > news >正文

高效去除字符串末尾重复单元的 KMP 前缀函数优化算法实现

一、背景与问题描述

  • 需求:给定字符串 s,若其末尾存在某段最小单元重复多次,则仅保留一个单元,其它折叠删除;否则保持原样。

  • 示例

    • "abcabcabc""abc"
    • "hellohello""hello"
    • "谢谢您谢谢您谢谢您""谢谢您"
    • "no repeat here""no repeat here"

要求支持 Unicode(按 rune 单位处理),并且只有当末尾某模式重复次数 ≥2 时才进行去重。

二、朴素枚举算法

1. 算法思路

  1. 将字符串转为 []rune,长度记为 n

  2. 枚举可能的最小单元长度 l1n/2

  3. 将末尾 l 个 rune 取为“模式”(pattern);

  4. 从末尾开始,每次向前跳 l,比较长度为 l 的子串是否与模式相同,统计连续匹配次数 count

  5. count ≥ 2,说明末尾有 ≥2 次重复,将多余部分折叠,只保留一个模式:

    prefix := string(runes[:n-count*l])
    return prefix + string(pattern)
    
  6. 若所有 l 都无法满足,返回原串。

2. 代码实现

// removeSuffixRepeats 去掉 s 中末尾连续重复的最小单元,只保留一个
func removeSuffixRepeats(s string) string {runes := []rune(s)n := len(runes)for l := 1; l <= n/2; l++ {pattern := runes[n-l:]count := 0for pos := n; pos >= l; pos -= l {match := truefor i := 0; i < l; i++ {if runes[pos-l+i] != pattern[i] {match = falsebreak}}if match {count++} else {break}}if count >= 2 {prefix := string(runes[:n-count*l])return prefix + string(pattern)}}return s
}

3. 复杂度分析

  • 时间复杂度:外层 l 枚举约 n/2 次,内层每次最坏比较 O(l),并做 n/l 次匹配,整体

    ∑l=1n/2O((n/l)×l)=O(∑l=1n/2n)=O(n2).\sum_{l=1}^{n/2} O\bigl((n/l)\times l\bigr)= O\bigl(\sum_{l=1}^{n/2} n\bigr)= O(n^2). l=1n/2O((n/l)×l)=O(l=1n/2n)=O(n2).

  • 空间复杂度O(n),用于存储 []rune

适用场景:短字符串或对性能要求不高时实现简单直观。

三、KMP 前缀函数优化

1. 核心原理

  • 目标:在 O(n) 时间内找出末尾可折叠的最小单元并去重。
  • 思路:将字符串反转,末尾重复后缀转换为开头重复前缀;对反转串计算 KMP 前缀函数(π 函数),快速得到每个前缀最长真前缀-后缀长度;据此判断最小周期并验证是否至少重复两次。

2. 算法步骤

  1. 反转:令 revrunes 的倒序。

  2. 计算前缀函数
    rev 构造长度 n 的数组 pi,其中

    π[i]=max⁡{ k<i+1∣rev[0:k]=rev[i−k+1:i+1]}.\pi[i] = \max\{\,k < i+1 \mid rev[0:k] = rev[i-k+1:i+1]\}. π[i]=max{k<i+1rev[0:k]=rev[ik+1:i+1]}.

    时间 O(n)。

  3. 扫描周期:对于每个前缀长度 L = i+1,最小周期 p = L - π[i]。若 L % p == 0 && L/p ≥ 2,说明该前缀由长度 p 的模式重复 ≥2 次。此模式即反转后缀,反转回去即可得到原串末尾的单元。

  4. 拼接结果:找到第一个(最长)可折叠后缀,保留原串前缀 runes[:n-L],再加一个模式。

3. 优化代码

package mainimport "fmt"// removeSuffixRepeatsOptimized O(n) 实现
func removeSuffixRepeatsOptimized(s string) string {runes := []rune(s)n := len(runes)if n < 2 {return s}// 1. 反转rev := make([]rune, n)for i, r := range runes {rev[n-1-i] = r}// 2. 计算前缀函数 pipi := make([]int, n)for i := 1; i < n; i++ {j := pi[i-1]for j > 0 && rev[i] != rev[j] {j = pi[j-1]}if rev[i] == rev[j] {j++}pi[i] = j}// 3. 扫描前缀找最小周期for i := n - 1; i > 0; i-- {L := i + 1p := L - pi[i]if pi[i] > 0 && L%p == 0 && L/p >= 2 {// 反转回最小单元pat := make([]rune, p)for k := 0; k < p; k++ {pat[p-1-k] = rev[k]}// 拼接结果return string(runes[:n-L]) + string(pat)}}return s
}func main() {examples := []string{"谢谢您,谢谢您,谢谢您","谢谢您谢谢您谢谢您","abcabcabc","hellohello","no repeat here",}for _, ex := range examples {fmt.Printf("%q -> %q\n", ex, removeSuffixRepeatsOptimized(ex))}
}

4. 复杂度分析

  • 时间复杂度:反转 O(n) + 前缀函数 O(n) + 扫描 O(n) = O(n)
  • 空间复杂度:O(n),多了一个反转切片 revpi 数组。

适用场景:长字符串、大批量数据或对性能敏感时推荐使用。

四、对比与实践建议

特性朴素枚举算法KMP 优化算法
时间复杂度O(n²)O(n)
代码复杂度简单易懂稍复杂
适用场景短字符串或偶发调用大规模文本处理
实现难度
  • 短小字符串:可直接使用枚举版本,代码直观,维护成本低。
  • 性能敏感:强烈推荐 KMP 版本,能够在线性时间内完成重复检测与去重。

五、总结与扩展

本文从最直观的枚举匹配算法切入,详细剖析了字符串末尾重复折叠的逻辑,并在此基础上借助 KMP 前缀函数,将时间复杂度从 O(n²) 优化到 O(n)。掌握这一思路后,还可进一步:

  1. 哈希滚动:结合双向哈希快速比较子串,减少常数;
  2. 后缀自动机:用于更通用的后缀匹配和重复检测;
  3. 并行化:针对超大文本,可将检测过程并行分片处理。

希望本文能帮助你在文本预处理、日志清洗、聊天记录去重等场景中,高效地解决末尾重复折叠问题。欢迎在评论中交流优化思路!

http://www.dtcms.com/a/285106.html

相关文章:

  • VR 远程系统的沉浸式协作体验​
  • SpringBoot 使用MyBatisPlus
  • 在windows平台上基于OpenHarmony sdk编译三方库并暴露给ArkTS使用(详细)
  • VSCODE常规设置
  • No catalog entry ‘md5‘ was found for catalog ‘default‘. 的简单解决方法
  • 学习软件测试的第十八天
  • 一款基于PHP开发的不良事件上报系统源码,适用于医院安全管理。系统提供10类事件类别、50余种表单,支持在线填报、匿名上报及紧急报告。
  • 前端防复制实战指南:5 种主流方案效果对比与实现
  • Ubuntu20.04上安装Anaconda
  • 磁盘分区(D盘分给C盘)
  • 【Triton 教程】triton_language.zeros_like
  • 跨域通信inframe高级
  • docker安装、启动jenkins服务,创建接口自动化定时任务(mac系统)
  • Web APIs 知识复习1
  • 基于STM32闭环步进电机控制系统设计说明
  • 【Linux庖丁解牛】— 信号捕捉!
  • SVG基础语法:绘制点线面的简单示例
  • Selenium 启动的浏览器自动退出问题分析
  • 使用Collections.max比较Map<String, Integer>中的最大值
  • C语言基础6——数组
  • 元宇宙与Web3的深度融合:构建沉浸式数字体验的愿景与挑战
  • 2020717零碎写写
  • Python 异步编程之 async 和 await
  • ThreadLocal源码解析
  • Mac OS上docker desktop 替代方案
  • Linux 下按字节分割与合并文件
  • 压力大为啥想吃甜食
  • wireshark的常用用法
  • C++ Lambda 表达式详解:从入门到实战
  • Leetcode 03 java