SA-BOUND returns the lower bound or the upper bound of the pattern full
This proof shows: correctness, termination, complexity.
Let S=S[0…n−1], SA its suffix array, P=P[0…m−1] with m≥0. Write Tq=SSA[q] for the suffix of rank q and Tqm for its prefix of length min(m,∣Tq∣). Add T−1, smaller than every string, and Tn, larger than every string, with lcp(T−1,x)=lcp(Tn,x)=0 for each x. Write lcp(a,b) for the length of the longest common prefix of Ta and Tb.
Call rank q low if Tqm<P (non-strict search) or Tqm≤P (strict search). By Lemma 2 of the suffix array proof, the Tqm are sorted, so the low ranks are 0,…,b−1 for some b, and b is the lower bound (or the upper bound). Count −1 as low and n as not low.
Fact A (order of sorted strings). For a<c among −1,…,n and d=lcp(a,c): either Ta has length d, or both have a symbol at position d and Ta[d]<Tc[d] (with the virtual ranks this holds by convention). This is the definition of Ta<Tc.
Fact B. For a<c<e, lcp(a,e)=min(lcp(a,c),lcp(c,e)) when all three are real, and lcp(a,e)≤lcp(a,c), lcp(a,e)≤lcp(c,e) in all cases. This is the usual property of sorted strings: lcp(a,e)=min(LCP[a+1],…,LCP[e]).
Invariant. At each test of line L3: lo is low, hi is not low, lo<hi, l=lcp(P,Tlo) and r=lcp(P,Thi), where lcp(P,x) is the length of the longest common prefix of P and x. Also Llcp[mid]=lcp(lo,mid) and Rlcp[mid]=lcp(mid,hi) for the midpoint computed in line L4. The last statement holds because (lo,hi) is always one of the intervals of the fixed recursion (−1,n)→(lo,mid),(mid,hi) used to build the tables.
Initialization. lo=−1 is low, hi=n is not, and l=r=0.
Maintenance. Let lo<mid<hi.
Line L6: l≥r and Llcp[mid]>l. Then Tmid agrees with Tlo on the first l+1 symbols, so lo=−1 and Tlo has a symbol at position l. If l=m, then P is a prefix of Tlo; since lo is low, the search is strict, and P is also a prefix of Tmid, so mid is low. If l<m, then Tlo and P differ at l, and Tlo[l]<P[l] because lo is low; Tmid[l]=Tlo[l]<P[l] and they agree before l, so Tmidm<P and mid is low, with lcp(P,Tmid)=l. In both cases lo←mid keeps the invariant with the same l.
Line L7: l≥r and Llcp[mid]=d<l. Then lo=−1 (else l=0), and by Fact A at position d either Tlo ends at d, which is impossible since Tlo agrees with P on l>d symbols, or Tlo[d]<Tmid[d] (or mid is virtual, which it is not). Since Tlo[d]=P[d] and d<l≤m, we get P[d]<Tmid[d] with agreement before d. So Tmidm>P, mid is not low in either search, and lcp(P,Tmid)=d. Setting hi←mid, r←d keeps the invariant.
Lines L9 and L10 are the mirror images, with Thi, Fact A for mid<hi, and "not low" in place of "low". For L9, Rlcp[mid]>r means Tmid agrees with Thi beyond position r; if r=m then P is a prefix of Thi, the search is not strict, and P is a prefix of Tmid, so mid is not low; if r<m, Thi has a symbol at r (else Thi would be a proper prefix of P, hence low), Thi[r]>P[r], and the same holds for Tmid. For L10, Rlcp[mid]=d<r gives Tmid[d]<Thi[d]=P[d] or Tmid ending at d, so mid is low with lcp(P,Tmid)=d.
Lines L8, L11, L12 to L15. If l≥r and Llcp[mid]=l, then Tmid agrees with Tlo, and so with P, on the first l symbols (if lo=−1 then l=0 and there is nothing to show). The case l<r, Rlcp[mid]=r is the same with hi. So line L12 starts with a k such that Tmid and P agree on k symbols, and ends with k=lcp(P,Tmid). Line L13 then decides lowness: if k=m, Tmidm=P, low exactly in the strict search; if Tmid ends at k<m, Tmidm=Tmid is a proper prefix of P, so low; otherwise the order of Tmid[k] and P[k] decides. Lines L14 and L15 move lo or hi and store k into l or r, which keeps the invariant.
Termination and result. Each pass replaces (lo,hi) by (lo,mid) or (mid,hi) with lo<mid<hi, so hi−lo decreases, and the loop ends with hi=lo+1. Then lo is low and hi is not; since the low ranks are −1,0,…,b−1, hi=b. So line L16 returns the lower bound for the non-strict search and the upper bound for the strict one, and the suffixes that start with P are exactly the ranks in [lb,ub).
Comparison count. With ⌊(lo+hi)/2⌋, the new gap is at most ⌈(hi−lo)/2⌉, so from hi−lo=n+1 there are at most ⌈lg(n+1)⌉ passes. The value max(l,r) never decreases: lines L6 and L9 change neither; line L7 runs with l≥r and sets r below l; line L10 runs with l<r and sets l below r; lines L14 and L15 store the end of line L12, which started at max(l,r). Each successful equality test in line L12 makes k, and then max(l,r), one larger than any earlier value, and max(l,r)≤m, so the successful tests number at most m over the whole search. Each pass has at most one failed equality test and at most one order test in line L13. So the search uses at most m+2⌈lg(n+1)⌉ symbol comparisons. ■