Algorithm Search⌘K

Proofs: 3SUM by sorting and two-pointer scan

These are the human proofs of 3SUM by sorting and two-pointer scan, at full length.

The sort-and-scan 3SUM algorithm reports each solution exactly once full

This proof shows: correctness, termination.

We use the pseudocode of the page. After line L1, A[0]≤A[1]≤⋯≤A[n−1]A[0] \le A[1] \le \cdots \le A[n-1] is the sorted input. A solution is a value triple (x,y,z)(x, y, z) with x≤y≤zx \le y \le z, x+y+z=tx + y + z = t, such that there are positions i<p<qi < p < q of AA with (A[i],A[p],A[q])=(x,y,z)(A[i], A[p], A[q]) = (x, y, z). Since AA holds the same multiset of values as the input, this is the same set of solutions as for the input list.

Claim. THREE-SUM(v,t)(v, t) terminates, and it reports each solution exactly once and nothing else.

Step 1: one sweep

Fix an ii that line L3 does not skip, and let x=A[i]x = A[i] and r=t−xr = t - x. Call a value pair (y,z)(y, z) good if y≤zy \le z, y+z=ry + z = r, and there are positions i<p<qi < p < q with A[p]=yA[p] = y and A[q]=zA[q] = z. We show that the sweep (lines L4 to L11) reports each good pair exactly once and nothing else.

Write GG for the set of good pairs that are not yet reported. We use this invariant at each test of line L5:

(Q) i+1≤loi + 1 \le lo and hi≤n−1hi \le n - 1. (a) Each (y,z)∈G(y, z) \in G has positions p<qp < q with lo≤p<q≤hilo \le p < q \le hi, A[p]=yA[p] = y and A[q]=zA[q] = z. (b) Each pair (y,z)(y, z) already reported in this sweep is good, and if lo≤hilo \le hi it has y<A[lo]y < A[lo] and z>A[hi]z > A[hi].

Initialization. Line L4 sets lo=i+1lo = i + 1 and hi=n−1hi = n - 1. Nothing is reported, so GG is the set of all good pairs, and by definition each of them has positions p,qp, q with i<p<q≤n−1i < p < q \le n - 1. So (a) holds, and (b) holds with nothing to check.

Maintenance. Assume (Q) and lo<hilo < hi. Line L6 sets s=x+A[lo]+A[hi]s = x + A[lo] + A[hi].

  • Case s<ts < t (line L7). Then A[lo]+A[hi]<rA[lo] + A[hi] < r. For each qq with lo<q≤hilo < q \le hi, A[lo]+A[q]≤A[lo]+A[hi]<rA[lo] + A[q] \le A[lo] + A[hi] < r, so no pair of positions in [lo,hi][lo, hi] with first position lolo gives a good pair. So the positions of (a) have p≥lo+1p \ge lo + 1, and (a) holds for the new lolo. Part (b) holds because A[lo+1]≥A[lo]A[lo + 1] \ge A[lo] and hihi does not change.

  • Case s>ts > t (line L8). Then A[lo]+A[hi]>rA[lo] + A[hi] > r. For each pp with lo≤p<hilo \le p < hi, A[p]+A[hi]≥A[lo]+A[hi]>rA[p] + A[hi] \ge A[lo] + A[hi] > r. So the positions of (a) have q≤hi−1q \le hi - 1, and (a) holds for the new hihi. Part (b) holds because A[hi−1]≤A[hi]A[hi - 1] \le A[hi].

  • Case s=ts = t (lines L9 to L11). Let y0=A[lo]y_0 = A[lo] and z0=A[hi]z_0 = A[hi]. Then y0≤z0y_0 \le z_0, y0+z0=ry_0 + z_0 = r, and the positions i<lo<hii < lo < hi show that (y0,z0)(y_0, z_0) is good. By (b), each pair reported before has y<A[lo]=y0y < A[lo] = y_0, so (y0,z0)(y_0, z_0) was not reported before: line L9 reports it for the first time, and it leaves GG.

    Now take any (y,z)∈G(y, z) \in G after this report, with positions lo≤p<q≤hilo \le p < q \le hi from (a). Then (y,z)≠(y0,z0)(y, z) \ne (y_0, z_0). If y=y0y = y_0 then z=r−y=z0z = r - y = z_0, which is not possible, so y≠y0y \ne y_0. If y<y0y < y_0, then A[p]<A[lo]A[p] < A[lo] with p≥lop \ge lo, which contradicts the sorted order. So y>y0y > y_0, and then z=r−y<r−y0=z0z = r - y < r - y_0 = z_0.

    Line L10 sets lolo to lo+1lo + 1 and then moves it right while lo<hilo < hi and A[lo]=A[lo−1]A[lo] = A[lo - 1]. Every position that it passes over holds the value y0y_0. Since A[p]=y>y0A[p] = y > y_0, position pp is not passed over, unless the loop stops early because lo=hilo = hi; we treat that below. Line L11 does the same for hihi with the value z0z_0, and position qq, with A[q]=z<z0A[q] = z < z_0, is not passed over. So if the loops stop because the next value differs, the new range still contains pp and qq, and (a) holds.

    If a loop stops because lo=hilo = hi, then all positions from the old lolo to the current one hold y0y_0 (or all positions from the current hihi to the old one hold z0z_0). Every remaining pair p<qp < q of the old range then has A[p]=y0A[p] = y_0 or A[q]=z0A[q] = z_0; by the paragraph above, no pair of GG has this, so GG is empty and (a) holds trivially.

    For (b): after line L10, either lo≥hilo \ge hi, or A[lo]≠y0A[lo] \ne y_0 and so A[lo]>y0A[lo] > y_0, which is at least as large as each yy reported so far. After line L11, in the same way, A[hi]<z0A[hi] < z_0 or lo≥hilo \ge hi. Each earlier reported pair had $z > $ the old A[hi]=z0A[hi] = z_0, so all reported pairs have z>A[hi]z > A[hi].

Termination of the sweep. Each iteration decreases hi−lohi - lo by at least 1: lines L7 and L8 by exactly 1, lines L10 and L11 by at least 2. The value starts at n−2−in - 2 - i, and the loop runs only while it is at least 1. So the sweep stops after at most n−2−in - 2 - i iterations.

End of the sweep. When the loop stops, lo≥hilo \ge hi, so no pair of positions lo≤p<q≤hilo \le p < q \le hi exists, and by (a) the set GG is empty: every good pair has been reported. By (b), and since each pair is reported only in the case where it was not reported before, no good pair is reported twice, and every reported pair is good.

Step 2: the outer loop

Let (x,y,z)(x, y, z) be a solution, with positions i<p<qi < p < q. Let i0i_0 be the smallest position with A[i0]=xA[i_0] = x. Then i0≤i<p<qi_0 \le i < p < q, so (y,z)(y, z) is a good pair for i0i_0. Position i0i_0 is not skipped by line L3, because i0=0i_0 = 0 or A[i0−1]<xA[i_0 - 1] < x. Also i0≤n−3i_0 \le n - 3, since q≤n−1q \le n - 1 and i0<p<qi_0 < p < q. By Step 1, the sweep for i0i_0 reports (x,y,z)(x, y, z).

The sweeps that line L3 does not skip are exactly the first positions of the distinct values of AA, so no two of them have the same xx. Each sweep reports its good pairs once, with its own xx. So each solution is reported exactly once, and each reported triple is a solution by Step 1.

The outer loop runs max⁡(n−2,0)\max(n - 2, 0) times and each sweep terminates, so the algorithm terminates. ■\blacksquare

Cost. By the termination argument, the total number of iterations of line L5 is at most ∑i=0n−3(n−2−i)=(n−1)(n−2)2\sum_{i=0}^{n-3}(n - 2 - i) = \frac{(n-1)(n-2)}{2}, and each costs O(1)O(1) besides the inner while loops of lines L10 and L11, whose steps are counted in the decrease of hi−lohi - lo. So the time after the sort is O(n2)O(n^2).

Back to 3SUM by sorting and two-pointer scan