我写了一个代码来填充KMP的前缀表。这是algorithm的小变化。我无法说服自己这个算法/实现在O(n)时间内运行。我很难弄清楚第二次递归调用对总运行时间的影响。有什么帮助吗?
public void fillFailTable(int[] failTable,String p){
failTable[failTable.length-1] = preLength(failTable,p);
}
private int preLength(int[] failTable,String s){
if(s.length() == 1){
return 0;
}
int n = s.length();
int k = preLength(failTable,s.substring(0,n-1));
failTable[n-2] = k;
if(s.charAt(k) == s.charAt(n-1)){
return k+1;
}else{
return preLength(failTable,s.substring(n-1-k));
}
}
答案 0 :(得分:0)
这实际上非常有趣(我仍然想知道为什么没有比我聪明的人回答这个问题)。请大家用这个解释,因为我不是100%肯定这甚至接近正确(尽管我可以100%告诉你这个方法在O(n)中运行,因为那是他们告诉我的大学几年前,但他们并没有费心去解释它,呃,所以我不得不自己想出来。
好的,让我们从s.length = 2的一个非常基本的例子开始。事先要提到两件事:
我们首先输入第一个preLength()方法(让我们调用它*),现在调用s.length = 1并立即返回0.现在因为我们只考虑最坏的情况(意思是s) .charAt(k)!= s.charAt(n-1))我们输入第二个preLength(),同时还有一个长度为1的字符串(因为n = 2且k = 0)。这个也立即向我们的*返回0。这结束了我们的方法调用。总共我们有3个方法调用。我们的*和两个preLength()。这是一张图片:
现在让我们看一个带有起始s.length = 3的示例。您可以注意到我们立即使用s.length = 2调用preLength(),并且从前面的示例中我们知道这个需要3个方法调用。现在我们需要记住,当方法preLength(2)返回此时它返回到我们的本地preLength(3),它现在将再次调用preLength(2)(else中的那个),这将再次需要3个方法调用。所以我们总共需要2 * 3 + 1个方法调用。这给了我们7.再次,这是一个图像(圆圈是preLength的调用,其长度为圆圈中的字符串):
现在您可以看到所有这些方法调用都是对称的 - 这是因为我们的k
总是等于0,这意味着第二个preLengt()将被调用与第一个字符串大小相同的字符串 - 当我们知道自s.length = m
m-1
以来f(m) = 2*f(m-1)+1
所需的字符数时,我们可以看到f(m)
需要多少字符串{} {1}}是告诉我们为大小为m
的字符串计算表所需的方法调用次数的函数。这是有效的,因为正如我之前所说的方法调用是对称的(那是因为在最坏的情况下k = 0总是和preLenght()总是返回0 ,因此2 *我们需要添加1个方法调用,我们引用的第一个。)
所以基本上随着输入的每次递增(m
的大小),计算时间增加2倍加1(2 * m + 1),据我所知,这意味着这种方法在最坏的情况下是O (n)的
正如我所说的那样,请尽量采取一些措施,但我希望这是有道理的。)