查找字符串的子串,使得子串的长度与其出现次数的乘积最大化
Posted
技术标签:
【中文标题】查找字符串的子串,使得子串的长度与其出现次数的乘积最大化【英文标题】:Finding substrings of string such that product of the length of the substring with its number of occurrences is maximized 【发布时间】:2014-09-08 00:38:06 【问题描述】:我正在考虑以下问题: 给定一个字符串 S,设第 ith 个子串的长度为 li,第 ith 个子串的出现次数为 o我。打印子字符串,使 li*oi 最大化。
对于这个问题,我有 O(n3) 解决方案(蛮力),我正在生成所有子字符串并找到具有最大值的子字符串。我的代码如下:
public static void solve(String S)
long max = Integer.MIN_VALUE;
String res = "";
for (int i = 0; i < S.length(); i++)
for (int j = 1; j <= S.length() - i; j++)
String s = S.substring(i, i + j);
int o = countOccurrences(S, s);
long p = (long) o * (long) s.length();
if (max < p)
max = p;
res = s;
System.out.println(res);
countOccurrences() 方法需要 O(n) 时间。我想知道是否有更有效的方法来实现这一点。
【问题讨论】:
这些事件可以相互重叠吗?例如。对于 S = 'ababa',答案是否 = 3 * 2 = 6? 是的。事件可以重叠。 【参考方案1】:这是一个线性时间算法:
-
在输入字符串上构建后缀树。这需要 O(n) 时间和空间。
在后序 DFS 中遍历后缀树,通过对其子节点的值求和来计算每个节点的子节点数。一旦节点的这个数量是已知的,就将它乘以它的字符串长度(这是从根开始的所有边的长度之和),并在必要时更新迄今为止的最佳总数。这也需要 O(n) 时间。
重点是
后缀树只包含线性数量的内部节点,并且 任何不对应于内部节点的子字符串都不能产生最大分数。这是因为当您从后缀树根跟踪它时,它必须到达某个边缘的“中途”——但您总是可以在不减少出现次数(即后代的数量)的情况下进一步扩展它,从而通过继续向下到下一个节点来增加分数。也可以使用后缀数组而不是后缀树来做到这一点,在这种情况下,它可能需要一个常数因子更少的内存,但在运行时间中添加一个对数因子。
【讨论】:
为什么后缀数组要在运行时间上加上对数因子?有算法在线性时间内构建 SA... @EvgenyKluev:我知道 SA 构建的线性时间算法,但我不确定 DFS 所需的时间。也许这也可以使用 LCA 表在线性时间内完成?请提出您的方法,或直接编辑我的答案。 有一种已知的方法来模拟 SA 中的 DFS,方法是预处理 LCA 数组以进行范围最小查询,然后使用指针对遍历数组。线性时间预处理和恒定时间查询RMQ是可能的,但并不容易实现。 有一篇很好的论文 "Replacing suffix trees with enhanced suffix arrays" 解释了如何在 SA 中执行 DFS 和许多其他任务。 @EvgenyKluev:谢谢!【参考方案2】:一个想法:您可以通过创建一个出现列表来缩短算法。这样,在运行 countOccurences
之前,您会检查它是否还没有被计算在内,并且您不会再次运行 countOccurences
来处理同样的事件。
这是第一次优化,应该还有其他优化,但这是一个开始。
【讨论】:
【参考方案3】:大概你可以用你剩下的来计算最大可用的“分数”,从而避免检查超出某个点的子字符串。我猜它的扩展性仍然很差,但它会人为地降低你的 'n' 值。
例如,如果您有一个长度为 10 的字符串,并且您设法在前 3 个字符中两次获得 3 个字符的子字符串(即 0-2、3-5),那么您可以说 max = 6。那一点,一旦超过i=4,你的得分不能超过6,所以你可以停下来?
【讨论】:
【参考方案4】:改进的算法如下:-(伪代码)
int max = 0;
for(int i=0;i<str.length;)
occurences = get_index(str[i]);
for(int j=i+1;j<str.length;j++)
if(occurences.size()==0)
break;
for(int k=0;k<occurences.size();k++)
int next_ind = occurrences[k]+j-i;
if(next_ind==length||str[next_ind]!=str[j])
delete(occurences[k]);
int score = ocurrences.size()*(j-i+1);
if(max<score)
score = max;
时间复杂度:-
理想情况下,子字符串的出现次数应该像n,n/2,n/3....
一样减少到最大值,因此算法是n*n*(1+1/2+1/3...) = O(n^2*logn) as (1+1/2+1/3...1/n) => logn
。使用链表在O(1)
中可以很容易地完成删除。
【讨论】:
以上是关于查找字符串的子串,使得子串的长度与其出现次数的乘积最大化的主要内容,如果未能解决你的问题,请参考以下文章