这里已经有两个答案是正确的,但我经常认为一个完整的证明可以让事情变得更清楚。你说你想要一个 9 岁孩子的答案,但我认为这是不可行的(我认为很容易被愚弄认为这是真的
,而实际上没有任何直觉知道为什么它是真的)。也许解决这个答案会有所帮助。
首先,外部循环运行n
时间很清楚,因为i
在循环内没有修改。循环中唯一可以运行多次的代码是块
while (j > 0 && s[i] != s[j])
{
j = pi[j-1]
}
那么它可以运行多少次?请注意,每次满足该条件时,我们都会减少其值,j
此时最多为
pi[i-1]
。如果它达到 0,则while
循环完成。要了解为什么这很重要,我们首先证明一个引理(你是一个非常聪明的 9 岁孩子):
pi[i] <= i
这是通过归纳来完成的。pi[0] <= 0
因为它在初始化时设置过一次,pi
并且再也没有被触及过。然后我们归纳地让0 < k < n
并假设该主张成立0 <= a < k
。考虑 的值p[k]
。它在行中精确设置一次pi[i] = j
。那么可以有多大j
?pi[k-1] <= k-1
它是通过归纳初始化的。在 while 块中,它可能会更新为pi[j-1] <= j-1 < pi[k-1]
. 通过另一个迷你感应,您可以看到它j
永远不会增加过去pi[k-1]
。因此,在
while
循环之后我们仍然有j <= k-1
. 最后它可能会增加一次,所以我们有
j <= k
,所以pi[k] = j <= k
(这是我们需要证明来完成我们的归纳)。
现在回到原点,我们问“我们可以减少多少次”的值
j
?有了我们的引理,我们现在可以看到循环的每次迭代while
都会单调减少 的值j
。特别是我们有:
pi[j-1] <= j-1 < j
那么这个可以运行多少次呢?大多数pi[i-1]
时候。精明的读者可能会认为“你什么都没证明!我们有pi[i-1] <= i-1
,但它在 while 循环内,所以它仍然是O(n^2)
!”。稍微精明的读者会注意到这个额外的事实:
无论我们运行多少次,我们都会j = pi[j-1]
减小其值,pi[i]
从而缩短循环的下一次迭代!
例如,假设j = pi[i-1] = 10
. 但是在循环的大约 6 次迭代之后,while
我们
j = 3
假设它在s[i] == s[j]
so 行中增加了 1 j = 4 = pi[i]
。那么在外循环的下一次迭代中,我们从j = 4
... 开始,所以我们最多只能执行while
4 次。
难题的最后一块是++j
每个循环最多运行一次。所以我们的pi
向量中不能有这样的东西:
0 1 2 3 4 5 1 6 1 7 1 8 1 9 1
^ ^ ^ ^ ^
Those spots might mean multiple iterations of the while loop if this
could happen
为了使它真正正式,您可以建立上面描述的不变量,然后使用归纳来显示该循环运行的总次数,与最多相加。由此可见,循环运行的总次数是,这意味着整个外循环具有复杂性:while
pi[i]
i
while
O(n)
O(n) // from the rest of the outer loop excluding the while loop
+ O(n) // from the while loop
=> O(n)