Level 4 — ApplicationString Algorithms

String Algorithms

60 minutes60 marksprintable — key stays hidden on paper

Level: 4 (Application — novel problems, no hints) Time limit: 60 minutes Total marks: 60

Answer all questions. Show all working. Where an algorithm is requested, state the invariant or recurrence you rely on.


Question 1 — KMP failure function & borders (12 marks)

Consider the pattern P = "abacabab".

(a) Construct the KMP failure (prefix) function array π\pi for P, showing each entry. (6)

(b) Using only your π\pi array (no re-scanning), list all proper borders of the full string P (a border is a proper prefix that is also a suffix). (3)

(c) A string SS of length nn has smallest period pp iff its longest border has length npn - p. Determine whether P has a period that divides its length, and state whether P is a repetition of a smaller block. (3)


Question 2 — Z-algorithm applied (12 marks)

Let T = "aabxaayaab".

(a) Compute the full Z-array Z[0..9]Z[0..9] for T. (Take Z[0]Z[0] to be undefined/0 by convention.) (6)

(b) You are given a pattern PP and text XX. Explain precisely how to use a single Z-array computation on the string P+#+XP + \# + X (where # is a separator not in the alphabet) to find all occurrences of PP in XX, and state the exact condition on ZZ values that signals a match. (4)

(c) Using your rule from (b), find all occurrences of P = "aab" in X = "xaayaab". (2)


Question 3 — Rabin-Karp rolling hash (12 marks)

Use a polynomial rolling hash with base b=31b = 31 and modulus q=101q = 101, mapping a1\to1, b2\to2, c3\to3. The hash of a window c0c1cm1c_0c_1\dots c_{m-1} is H=(i=0m1v(ci)bm1i)modq.H = \Big(\sum_{i=0}^{m-1} v(c_i)\, b^{\,m-1-i}\Big) \bmod q.

(a) Compute the hash of the pattern P = "cab". (3)

(b) For text T = "abcaba", compute the hash of the first window "abc", then use the rolling update to obtain the hash of the next window "bca". Show the rolling formula you use. (5)

(c) A spurious hit (hash collision without a true match) occurs at some window in T for pattern P. Identify it, if any, among all windows of length 3, and state how Rabin-Karp avoids reporting it as a real match. (4)


Question 4 — Manacher's algorithm (12 marks)

Consider S = "abacaba".

(a) List the length of the longest palindromic substring centred at each character (odd-length centres only). Present this as an array of radii. (6)

(b) State the longest palindromic substring of S and its length. (2)

(c) Manacher's algorithm reuses previously computed radii using a "current rightmost palindrome" [l,r][l, r]. When expanding centre ii with mirror i=l+rii' = l + r - i, give the exact initialisation of the radius at ii before attempting expansion, and explain why this initialisation is always valid. (4)


Question 5 — Multi-pattern & suffix structures (12 marks)

(a) You must search a text of length nn for kk patterns of total length MM. Compare the total worst-case time of (i) running KMP independently per pattern versus (ii) building one Aho-Corasick automaton. Give both complexities and state when (ii) is strictly better. (4)

(b) For the set of patterns {"he", "she", "his", "hers"}, describe the failure (suffix) link target of the trie node reached by the path "she", and explain what output(s) are reported when the automaton reaches that node while scanning text "ushers". (4)

(c) Given the string S = "banana" (append $ conceptually), list all 7 suffixes and give the suffix array (the sorted starting indices). Then give the LCP array value between the first two adjacent suffixes in sorted order. (4)


Answer keyMark scheme & solutions

Question 1 (12)

(a) P = "abacabab", indices 0–7.

Build π[i]\pi[i] = length of longest proper prefix that is also suffix of P[0..i].

i char π
0 a 0
1 b 0
2 a 1
3 c 0
4 a 1
5 b 2
6 a 3
7 b 2

Reasoning at i=6: prefix "abaca", matching "a","ab","aba" → π=3 (since P[3]... actually compare: previous π[5]=2, P[6]='a', P[2]='a' match → π=3). At i=7: from π=3, P[7]='b', P[3]='c' mismatch, fall back to π[2]=1, P[7]='b', P[1]='b' match → π=2. (6, ~1 per correct entry from i=2 onward)

(b) Borders of full P come from iterating π\pi on the last index (7): π[7]=2π[1]=0\pi[7]=2 \to \pi[1]=0. So border lengths are 2 and 0 → the only non-empty proper border is "ab" (length 2). (3)

(c) Longest border length = 2, so smallest period p=n2=82=6p = n - 2 = 8 - 2 = 6. Since 686 \nmid 8, P is not a whole-number repetition of a smaller block; it is not periodic in the "power of a block" sense. (3)


Question 2 (12)

(a) T = "aabxaayaab", indices 0–9.

i 0 1 2 3 4 5 6 7 8 9
char a a b x a a y a a b
Z 1 0 0 2 1 0 3 1 0

Checks: Z[1]Z[1]: T[1]='a'=T[0], T[2]='b'≠T[1]? compare prefix: T[0..]="aab", suffix at 1 "abx…" → match length 1. Z[4]Z[4]: "aay…" vs prefix "aab" → "aa" match, then 'y'≠'b' → 2. Z[7]Z[7]: suffix "aab" vs prefix "aab" → full 3. (6)

(b) Concatenate W=P+#+XW = P + \# + X. Compute ZZ over WW. For every position ii in the XX-portion (i.e. i>Pi > |P|), if Z[i]PZ[i] \ge |P| then PP occurs in XX starting at index iP1i - |P| - 1. The separator # (not in alphabet) guarantees Z[i]Z[i] can never exceed P|P|, so Z[i]=PZ[i] = |P| signals a full match. (4)

(c) W=W = aab#xaayaab, P=3|P|=3. Positions in X region needing Z=3Z=3: the suffix "aab" at end matches → occurrence. Index in X: the "aab" starts at X-index 4 (x a a y a a b → positions 4,5,6). So one occurrence at index 4 of X. (2)


Question 3 (12)

(a) P="cab", vv: c=3,a=1,b=2. b=31b=31. HP=(3312+131+2)mod101H_P = (3\cdot31^2 + 1\cdot31 + 2) \bmod 101. 312=96131^2 = 961. 3961=28833\cdot961 = 2883. 2883+31+2=29162883+31+2 = 2916. 2916mod1012916 \bmod 101: 10128=2828101\cdot28=2828, 29162828=882916-2828=88. HP=88H_P = 88. (3)

(b) Window "abc": vv=a1,b2,c3. H=(1961+231+3)mod101=(961+62+3)mod101=1026mod101H = (1\cdot961 + 2\cdot31 + 3)\bmod101 = (961+62+3)\bmod101 = 1026 \bmod 101. 10110=1010101\cdot10=1010, 10261010=161026-1010=16. So Habc=16H_{abc}=16.

Rolling to "bca": remove leading a1, add trailing a1: Hnew=((Holdv(c0)bm1)b+v(cnew))modq.H_{new} = \big((H_{old} - v(c_0)\cdot b^{m-1})\cdot b + v(c_{new})\big)\bmod q. bm1=b2=961961mod101b^{m-1}=b^2=961\equiv 961\bmod101. 1019=909101\cdot9=909, 961909=52961-909=52. So 96152961\equiv52. Hnew=((16152)31+1)mod101=((36)31+1)mod101=(1116+1)mod101=1115mod101H_{new} = ((16 - 1\cdot52)\cdot31 + 1)\bmod101 = ((-36)\cdot31 + 1)\bmod101 = (-1116+1)\bmod101 = -1115 \bmod 101. 10111=1111101\cdot11=1111, 1115+1212=97-1115+1212=97 (add 10112=1212101\cdot12=1212): 9797. Verify directly: "bca" = b2,c3,a1 → 2961+331+1=1922+93+1=20162\cdot961+3\cdot31+1 = 1922+93+1=2016; 2016mod1012016\bmod101: 10119=1919101\cdot19=1919, 20161919=972016-1919=97. ✓ Hbca=97H_{bca}=97. (5)

(c) Pattern hash 88. Compute all length-3 window hashes of T="abcaba":

  • "abc"→16
  • "bca"→97
  • "cab": c3,a1,b2 → 3961+31+2=2916883\cdot961+31+2=2916\to88 ✓ hash = 88
  • "aba": a1,b2,a1 → 961+62+1=1024mod101961+62+1=1024\bmod101: 10110=1010101\cdot10=1010, →14.

Window "cab" at index 2 has hash 88 = HPH_P. Here the string "cab" actually equals P, so it is a true match, not spurious. Checking all windows, no window has hash 88 with a differing string → no spurious hit occurs for this text. Rabin-Karp always confirms a hash match with a direct character comparison, so any collision would be rejected; here the only hash match is genuine. (4) (Award full marks for correctly finding no spurious hit and stating the verification step.)


Question 4 (12)

(a) S="abacaba" (indices 0–6). Odd-centre palindrome radius (number of characters each side, so palindrome length = 2·radius+1):

i char radius pal length
0 a 0 1
1 b 0 1
2 a 1 3 (aba)
3 c 3 7 (abacaba)
4 a 1 3 (aba)
5 b 0 1
6 a 0 1

(6)

(b) Longest palindromic substring = "abacaba", length 7 (centre index 3). (2)

(c) Before expanding centre ii inside [l,r][l,r] with mirror i=l+rii' = l+r-i, initialise P[i]=min(P[i],  ri).P[i] = \min\big(P[i'],\; r - i\big). This is valid because the substring S[l..r]S[l..r] is a palindrome, so the neighbourhood of ii mirrors that of ii' at least up to the boundary rr; we can safely copy P[i]P[i'] but must cap it at rir-i since beyond rr the mirror symmetry is not guaranteed and must be verified by explicit expansion. (4)


Question 5 (12)

(a) (i) KMP per pattern: preprocess each pattern O(pj)O(|p_j|) and scan text once per pattern O(n)O(n) → total O(kn+M)O(kn + M). (ii) Aho-Corasick: build automaton O(M)O(M) (over fixed alphabet) and single text scan O(n+occ)O(n + \text{occ}) → total O(M+n+occ)O(M + n + \text{occ}). Aho-Corasick is strictly better when kk is large (many patterns), since it replaces the knkn term by a single nn scan. (4)

(b) In the trie for {he, she, his, hers}, the node reached by "she" has a failure link to the node reached by "he" (the longest proper suffix of "she" that is a prefix of some pattern). Because "he" is itself a complete pattern, reaching the "she" node while scanning "ushers" reports both "she" (the node's own pattern) and "he" (via the output link following the failure link). At the position ending "...she" in "ushers", outputs = {she, he}. (4)

(c) Suffixes of S="banana":

0: banana
1: anana
2: nana
3: ana
4: na
5: a
6: (empty, $)

Sorted (with $ smallest): order of starting indices: $(6), a(5), ana(3), anana(1), banana(0), na(4), nana(2).

Suffix array = [6, 5, 3, 1, 0, 4, 2] (or [5,3,1,0,4,2] if $ omitted).

First two adjacent real suffixes in sorted order: a(5) and ana(3). LCP = 1 (common prefix "a"). (4)


[
  {"claim":"KMP pi array for abacabab ends with border length 2","code":"P='abacabab'\npi=[0]*len(P)\nk=0\nfor i in range(1,len(P)):\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,0,1,2,3,2])"},
  {"claim":"Hash of cab base31 mod101 is 88","code":"v={'a':1,'b':2,'c':3}\nb=31; q=101\nP='cab'\nH=0\nfor c in P:\n    H=(H*b+v[c])%q\nresult = (H==88)"},
  {"claim":"Rolling hash of bca equals 97","code":"v={'a':1,'b':2,'c':3}\nb=31;q=101\ndef h(s):\n    H=0\n    for c in s: H=(H*b+v[c])%q\n    return H\nresult = (h('bca')==97 and h('abc')==16)"},
  {"claim":"Suffix array of banana (no sentinel) is [5,3,1,0,4,2]","code":"S='banana'\nsuf=sorted(range(len(S)), key=lambda i:S[i:])\nresult = (suf==[5,3,1,0,4,2])"},
  {"claim":"Manacher longest palindrome of abacaba has length 7","code":"S='abacaba'\nn=len(S)\nbest=0\nfor c in range(n):\n    r=0\n    while c-r>=0 and c+r<n and S[c-r]==S[c+r]:\n        r+=1\n    best=max(best,2*(r-1)+1)\nresult = (best==7)"}
]