Level 5 — MasteryString Algorithms

String Algorithms

90 minutes60 marksprintable — key stays hidden on paper

Chapter: 3.8 String Algorithms Level: 5 — Mastery (cross-domain: build / prove / analyze) Time limit: 90 minutes Total marks: 60

Instructions: Answer all questions. Provide rigorous proofs where asked. Pseudocode must be complete and unambiguous. Justify all complexity claims.


Question 1 — KMP Failure Function: Correctness & Amortized Analysis (24 marks)

Let P=p0p1pm1P = p_0 p_1 \dots p_{m-1} be a pattern. Define the failure function

π[i]=max{k:0ki, P[0k1]=P[ik+1i]}\pi[i] = \max\{\, k : 0 \le k \le i,\ P[0\ldots k-1] = P[i-k+1 \ldots i]\,\}

i.e. the length of the longest proper prefix of P[0i]P[0\ldots i] that is also a suffix of P[0i]P[0\ldots i].

(a) Compute π\pi by hand for P=ababcababaP = \texttt{ababcababa} (indices 0099). Show the array. (4 marks)

(b) Write complete pseudocode for building π\pi in O(m)O(m) time. (4 marks)

(c) Prove the key recurrence used in construction: if we are computing π[i]\pi[i] and k=π[i1]k = \pi[i-1], then the candidate lengths to test are exactly the sequence k,π[k1],π[π[k1]1],k, \pi[k-1], \pi[\pi[k-1]-1], \dots down to 00. Justify why this sequence enumerates all borders of P[0i1]P[0\ldots i-1]. (6 marks)

(d) Prove the failure-function construction runs in O(m)O(m) time using an amortized (potential) argument. Explicitly state your potential function and bound the total number of times the inner while loop executes across the whole run. (6 marks)

(e) Using π\pi, state and justify the formula for all lengths \ell of borders of the whole string PP (prefixes that are also suffixes), expressed via iterating π\pi. (4 marks)


Question 2 — Rabin–Karp: Probability of a False Match (20 marks)

Rabin–Karp hashes a length-mm window over alphabet {0,,d1}\{0,\dots,d-1\} as

h(S[ii+m1])=(j=0m1S[i+j]dm1j)modq,h(S[i\ldots i+m-1]) = \Big(\sum_{j=0}^{m-1} S[i+j]\, d^{\,m-1-j}\Big) \bmod q,

for a prime modulus qq.

(a) Derive the rolling-hash update formula that produces hi+1h_{i+1} from hih_i in O(1)O(1) time. State precisely the precomputed constant needed. (4 marks)

(b) Consider text length nn and one pattern. Suppose qq is a prime chosen uniformly at random from primes N\le N. Two distinct strings collide iff qq divides a fixed nonzero integer DD with D<dm|D| < d^{\,m}. Show that the number of prime divisors of DD is at most mlog2dm\log_2 d, and hence bound the probability that a fixed spurious window collides. (6 marks)

(c) Using (b) and a union bound, show the expected number of spurious (false) matches over the whole text is O ⁣(nmlogdπ(N))O\!\big(n \cdot \frac{m\log d}{\pi(N)}\big) where π(N)\pi(N) is the prime-counting function. Then argue how large NN must be (asymptotically) so that the expected total spurious matches is O(1)O(1), and conclude the expected running time is O(n+m)O(n+m). (6 marks)

(d) Give one concrete adversarial input family that forces the naive O(nm)O(nm) matcher into Θ(nm)\Theta(nm) worst-case time, and explain in one sentence why Rabin–Karp's worst case is still Θ(nm)\Theta(nm) despite (c). (4 marks)


Question 3 — Manacher ⇄ Z-array: Palindromes and Physics of Symmetry (16 marks)

(a) State Manacher's algorithm's core invariant (the "mirror" reflection about the current palindrome center) and prove the initialization

p[i]  min(ri, p[2ci])p[i] \ \geq\ \min\big(r - i,\ p[\,2\cdot c - i\,]\big)

where cc is the current center and rr its right boundary. Explain physically why this is exactly a reflection symmetry: relate the mirror index 2ci2c-i to a reflection map on the number line about point cc. (6 marks)

(b) Run Manacher (using the transformed string with separators #) on S=abaabaS = \texttt{abaaba}. Give the pp-array on the transformed string and state the longest palindromic substring and its length. (6 marks)

(c) The Z-array Z[i]Z[i] gives the length of the longest substring starting at ii that matches a prefix of SS. Prove that pattern occurrences of PP in TT can be found by building the Z-array of P\T(where(where$isaseparatornotineitherstring):anoccurrenceatpositionis a separator not in either string): an occurrence at positionjofofTcorrespondsexactlytocorresponds exactly toZ[,|P|+1+j,] = |P|$. (4 marks)

Answer keyMark scheme & solutions

Question 1

(a) For P=ababcababaP=\texttt{ababcababa}:

i 0 1 2 3 4 5 6 7 8 9
char a b a b c a b a b a
π[i]\pi[i] 0 0 1 2 0 1 2 3 4 3

Reasoning at i=9i=9: longest border of ababcababa. Prefix aba = suffix aba (length 3). Length 4 abab ≠ suffix baba, so π[9]=3\pi[9]=3. (4 marks: −1 per error, max 4.)

(b) Pseudocode O(m)O(m):

build_pi(P, m):
    pi[0] = 0
    k = 0
    for i = 1 to m-1:
        while k > 0 and P[i] != P[k]:
            k = pi[k-1]
        if P[i] == P[k]:
            k = k + 1
        pi[i] = k
    return pi

(4 marks: correct while-fallback 2, increment/assign 2.)

(c) Claim: the borders of a string w=P[0..i1]w = P[0..i-1] are exactly the prefixes of lengths in the sequence k0=π[i1], k1=π[k01], k2=π[k11],k_0 = \pi[i-1],\ k_1 = \pi[k_0-1],\ k_2 = \pi[k_1-1],\dots terminating at 00.

Proof. A border of ww of length bb is a proper prefix equal to a suffix. The longest border has length π[i1]=k0\pi[i-1]=k_0. Suppose bb is any border with 0<b<k00<b<k_0. Since the length-k0k_0 border u=P[0..k01]u = P[0..k_0-1] is both a prefix and suffix of ww, and b<k0b<k_0, the length-bb border of ww (being a suffix of ww of length <k0<k_0) is a suffix of uu; and being a prefix of ww it is a prefix of uu. Hence any border of ww shorter than k0k_0 is precisely a border of u=P[0..k01]u = P[0..k_0-1]. The longest border of uu has length π[k01]=k1\pi[k_0-1]=k_1. By induction the full set of border lengths is {k0,k1,}\{k_0,k_1,\dots\} enumerated by repeatedly applying π[1]\pi[\cdot-1] until reaching 00. This is a strictly decreasing chain (each π[k1]<k\pi[k-1]<k), so it terminates and lists every border exactly once. ∎ (6 marks: prefix∩suffix argument 3, inductive nesting of borders 2, termination/decreasing 1.)

(d) Potential argument. Let Φ=k\Phi = k (the current matched length variable). Initially Φ=0\Phi=0; always Φ0\Phi\ge0.

  • Each iteration of the outer for (m−1 iterations) increases kk by at most 11 (the single if increment). So total increase of Φ\Phi over the whole algorithm is m1\le m-1.
  • Each execution of the inner while body sets k=π[k1]<kk=\pi[k-1]<k, strictly decreasing Φ\Phi by at least 11.

Since Φ\Phi starts at 00, ends 0\ge0, and total increase is m1\le m-1, the total decrease is also m1\le m-1. Therefore the inner while body executes at most m1m-1 times in total across all outer iterations. Outer loop is O(m)O(m); total work O(m)+O(m)=O(m)O(m)+O(m)=O(m). ∎ (6 marks: potential defined 2, increase bounded 2, decrease = while-count bounded → O(m) 2.)

(e) All border lengths of the whole string PP (length mm): start with 0=π[m1]\ell_0=\pi[m-1], then j+1=π[j1]\ell_{j+1}=\pi[\ell_j-1], continuing while j>0\ell_j>0. Each such \ell is a length where a prefix equals a suffix of PP. Justification: identical to (c) applied to w=Pw=P (the whole string), i.e. i1=m1i-1=m-1. (4 marks.)


Question 2

(a) Removing leading digit and appending trailing digit:

hi+1=((hiS[i]dm1)d+S[i+m])modq.h_{i+1} = \Big( (h_i - S[i]\cdot d^{\,m-1})\cdot d + S[i+m] \Big) \bmod q.

Precompute the constant Dhigh=dm1modqD_{high} = d^{\,m-1} \bmod q. All operations mod qq; O(1)O(1) per shift. (4 marks: subtract top 2, multiply+add new 1, precomputed constant named 1.)

(b) If two distinct length-mm strings A,BA,B have equal hash, then q(val(A)val(B))=Dq \mid (val(A)-val(B))=D with D0D\ne0 and D<dm|D|<d^{\,m} (each is in [0,dm1][0, d^m-1]). An integer DD with D<dm|D|<d^m has at most log2D<log2dm=mlog2d\log_2|D| < \log_2 d^{\,m} = m\log_2 d prime divisors (each prime factor is 2\ge 2, and the product of tt primes 2t\ge 2^t; so 2tD<dmt<mlog2d2^t \le |D| < d^m \Rightarrow t < m\log_2 d). If qq is uniform among the π(N)\pi(N) primes N\le N, the probability qq is one of these mlog2d\le m\log_2 d bad primes is

Pr[collision]mlog2dπ(N).\Pr[\text{collision}] \le \frac{m\log_2 d}{\pi(N)}.

(6 marks: define D nonzero bounded 2, prime-divisor count via 2tD2^t\le|D| 2, probability ratio 2.)

(c) There are n\le n candidate windows; each non-matching window collides with probability mlog2dπ(N)\le \frac{m\log_2 d}{\pi(N)}. By union bound (linearity of expectation),

E[#spurious]nmlog2dπ(N)=O ⁣(nmlogdπ(N)).\mathbb{E}[\#\text{spurious}] \le n\cdot\frac{m\log_2 d}{\pi(N)} = O\!\Big(\frac{n\,m\log d}{\pi(N)}\Big).

By the prime number theorem π(N)N/lnN\pi(N)\sim N/\ln N. Choosing N=Θ(nm)N = \Theta(nm) gives π(N)=Θ(nm/log(nm))\pi(N)=\Theta(nm/\log(nm)), so

E[#spurious]=O ⁣(nmlogdnm/log(nm))=O(logdlog(nm)),\mathbb{E}[\#\text{spurious}] = O\!\Big(\frac{nm\log d}{nm/\log(nm)}\Big)=O(\log d\,\log(nm)) ,

and choosing N=Θ(nm2logd)N=\Theta(n m^2\log d) (or any NN with π(N)nmlogd\pi(N)\gg nm\log d) forces the expected count to O(1)O(1). Each true match verification costs O(m)O(m), but expected number of verifications (true + spurious) is O(occ+1)O(\text{occ}+1), so total expected time is O(n+m)O(n+m) (hashing) plus O(m)O(m) per genuine occurrence. ∎ (6 marks: union bound 2, PNT & choice of N 2, conclusion O(n+m) 2.)

(d) Naive worst case: T=aaaa...aT=\texttt{aaaa...a} (nn a's), P=aaa...abP=\texttt{aaa...ab} (length mm, all a's then a b). At every one of the nm+1n-m+1 shifts the naive matcher compares m1m-1 matching a's before failing on the b → Θ(nm)\Theta(nm). Rabin–Karp worst case is still Θ(nm)\Theta(nm) because an adversary knowing qq can craft inputs where every window's hash collides with the pattern hash, forcing O(m)O(m) verification at each of nn positions. (4 marks: example 2, RK worst-case reason 2.)


Question 3

(a) Invariant: maintain the rightmost-reaching palindrome with center cc and right boundary rr (radius p[c]p[c] so r=c+p[c]r=c+p[c]). For i<ri<r, let its mirror be i=2cii' = 2c-i. Because S[ct]=S[c+t]S[c-t]=S[c+t] for all tp[c]t\le p[c] (palindrome symmetry about cc), the substring around ii mirrors that around ii' as long as it stays inside [cp[c],c+p[c]][c-p[c],\,c+p[c]]. Hence p[i]min(ri, p[i])p[i]\ge \min(r-i,\ p[i']): we can copy the mirror's radius p[i]p[i'], but not beyond the current right boundary (only rir-i characters to the right are guaranteed symmetric). Beyond rr we must expand explicitly.

Physical interpretation: the map x2cxx\mapsto 2c-x is exactly a reflection (parity) about the point cc on the real line — the same reflection symmetry as a mirror at position cc. ii and i=2cii'=2c-i are equidistant from cc on opposite sides; the palindrome property is invariance of SS under this reflection within radius p[c]p[c]. (6 marks: invariant + copy bound 3, reflection map 2ci2c-i as parity/mirror 3.)

(b) S=abaabaS=\texttt{abaaba}. Transformed T=#a#b#a#a#b#a#T = \texttt{\#a\#b\#a\#a\#b\#a\#} (length 13, indices 0–12).

pp-array (radii on transformed string):

idx 0 1 2 3 4 5 6 7 8 9 10 11 12
char # a # b # a # a # b # a #
p 0 1 0 3 0 1 6 1 0 3 0 1 0

Max p=6p=6 at index 6 (the center # between the two middle a's) → longest palindrome length =6=6: substring abaaba (the whole string). (6 marks: transformed string 1, p-array correct 3, longest palindrome + length 2.)

(c) Build W = P\Twithwith$\notin P,T,, |P|=k.Forposition. For position jininT,itsindexin, its index in Wisisk+1+j.. Z[k+1+j]=lengthoflongestprefixof= length of longest prefix ofW(whichis(which isP$...)matchingthesubstringstartingat) matching the substring starting at k+1+j.Since. Since $appearsonlyonce(atindexappears only once (at indexk),thematchcannevercrossthe), the match can never cross the $boundary,soboundary, soZvaluevalue\le k.If. If Z[k+1+j]=k,then, then W[k+1+j \ldots k+1+j+k-1] = W[0\ldots k-1] = P,i.e., i.e. T[j\ldots j+k-1]=P:anexactoccurrence.Converselyanoccurrencegivesalength: an exact occurrence. Conversely an occurrence gives a length-kprefixmatch,anditcannotexceedprefix match, and it cannot exceedkbecausethebecause the(k{+}1)thprefixcharacteris-th prefix character is $whichisabsentinwhich is absent inT.Henceoccurrences. Hence occurrences \iff Z[k+1+j]=k$. ∎ (4 marks: separator blocks overflow 2, iff 2.)

[
  {"claim":"pi array for ababcababa is [0,0,1,2,0,1,2,3,4,3]",
   "code":"P='ababcababa'\nm=len(P)\npi=[0]*m\nk=0\nfor i in range(1,m):\n    while k>0 and P[i]!=P[k]:\n        k=pi[k-1]\n    if P[i]==P[k]:\n        k+=1\n    pi[i]=k\nresult = (pi==[0,0,1,2,0,1,2,3,4,3])"},
  {"claim":"Rabin-Karp rolling update matches direct hash on random data (mod q, base d)",
   "code":"import random\nrandom.seed(