You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中相邻相似最短前缀元素分组的性能优化咨询

字符串压缩性能优化问题

输入示例

val test: String = "1,1,12,1,1,12,13"
val test2: String = "1,1,12,1,1,12,13,1,1,12"

期望输出

test --> 2{1,1,12},13
test2 --> 2{1,1,12},13,2{1},12

当前实现代码

def shorten_prefix(pfx:String, sfx: String): (Integer, String, String) = {
  var count = 1
  var _sfx = sfx
  while (_sfx.startsWith(pfx)) {
    count = count + 1
    _sfx = _sfx.splitAt(pfx.length)._2
  }
  val _tmp =  s"$count{${pfx}}"
  (count, _tmp, _sfx)
}
def find_shortest_repr(input: String): String= {
  var possible_variants: mutable.ListBuffer[String] = new mutable.ListBuffer[String]()
  if (input.isEmpty) {
    return ""
  }

  val size = input.length
  for (i <- 1 until  size + 1){
    val (prefixes, suffixes) = input.splitAt(i)
    val (counter, shortened, new_ending) = shorten_prefix(prefixes, suffixes)

    val shortest_ending: String = find_shortest_repr(new_ending)
    val tmp = shortened ++ shortest_ending
    possible_variants += (tmp)
  }
  possible_variants.minBy(f => f.length)
}

def compress(x: String)= {
  val _tmp = find_shortest_repr(x)
  val regex = "(,\\})"
  val subst = "},"
  _tmp.replaceAll(regex, subst).dropRight(1)
}

println(compress(test))
println(compress(test2))

性能问题

当前递归枚举所有前缀的方案,随着输入字符串长度增加,计算量呈指数级增长,耗时急剧上升。

优化技巧与方法

1. 记忆化缓存

递归函数find_shortest_repr会重复计算相同子串的最短表示,比如不同前缀拆分后可能得到相同的new_ending子串,多次重复计算完全没必要。用哈希表缓存已计算的子串结果,避免重复递归:

import scala.collection.mutable

def find_shortest_repr(input: String, cache: mutable.HashMap[String, String]): String= {
  if (input.isEmpty) return ""
  if (cache.contains(input)) return cache(input)
  
  var possible_variants: mutable.ListBuffer[String] = new mutable.ListBuffer[String]()
  val size = input.length
  for (i <- 1 until size + 1){
    val (prefixes, suffixes) = input.splitAt(i)
    val (counter, shortened, new_ending) = shorten_prefix(prefixes, suffixes)
    val shortest_ending: String = find_shortest_repr(new_ending, cache)
    val tmp = shortened ++ shortest_ending
    possible_variants += tmp
  }
  val result = possible_variants.minBy(_.length)
  cache.put(input, result)
  result
}

def compress(x: String)= {
  val cache = mutable.HashMap[String, String]()
  val _tmp = find_shortest_repr(x, cache)
  val regex = "(,\\})"
  val subst = "},"
  _tmp.replaceAll(regex, subst).dropRight(1)
}

2. 限制前缀的有效范围

  • 前缀长度上限:前缀长度不能超过输入字符串长度的一半,因为至少要重复一次才会有压缩收益,超过一半的前缀无法找到重复后缀,直接跳过。
  • 基于元素拆分前缀:输入是逗号分隔的元素序列,先拆分为元素列表,枚举完整的元素子序列作为前缀,避免拆分到元素中间(比如不要把12拆成1和2),减少无效计算:
val elements = input.split(",").toList
// 枚举元素子序列长度,而非原始字符串字符长度
for (elemCount <- 1 to elements.length/2) {
  val prefixElems = elements.take(elemCount)
  val prefixStr = prefixElems.mkString(",")
  // 后续检测重复逻辑
}

3. 提前剪枝

在生成候选结果时,若当前临时字符串长度已经大于已知的最短长度,直接跳过该分支的递归计算,避免不必要的开销。比如维护一个当前最短长度变量,当shortened.length + 已知最短后缀长度超过当前最小值,就停止处理该前缀。

4. 改用动态规划代替递归

用动态规划数组dp[i]表示前i个元素的最短压缩结果,通过迭代计算每个位置的最优解,避免递归栈开销和重复计算:

def compressDP(input: String): String = {
  val elements = input.split(",").toList
  val n = elements.length
  val dp = Array.fill(n+1)("")
  
  for (i <- 1 to n) {
    dp(i) = elements.take(i).mkString(",")
    for (j <- 1 until i) {
      val subSeq = elements.slice(j, i)
      val subLen = subSeq.length
      var count = 1
      var pos = j - subLen
      while (pos >= 0 && elements.slice(pos, pos+subLen) == subSeq) {
        count +=1
        pos -= subLen
      }
      if (count > 1) {
        val candidate = s"$count{${subSeq.mkString(",")}},${dp(pos+subLen)}"
        if (candidate.length < dp(i).length) {
          dp(i) = candidate
        }
      }
      val candidate2 = s"${dp(j)},${elements.slice(j, i).mkString(",")}"
      if (candidate2.length < dp(i).length) {
        dp(i) = candidate2
      }
    }
  }
  dp(n).replaceAll(",}", "}").stripSuffix(",")
}

5. 优化重复前缀检测

当前shorten_prefix用startsWith循环检测效率低,可改用KMP算法的前缀函数,预先计算字符串的重复周期,快速判断前缀是否重复多次,减少循环次数。

内容的提问来源于stack exchange,提问作者Flow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 03:33:29