3.3.7 · D4Hashing

Exercises — Amortized O(1) operations

3,946 words18 min readBack to topic

A reminder of the two facts we lean on the whole way down:

Figure — Amortized O(1) operations

The picture above is the mental model for every problem below: the tall amber bars are rare expensive resizes, the short cyan bars are cheap writes, and the flat white line is the constant average they add up to.


Level 1 — Recognition

Recall Solution 1.1

(a) Cheap writes contribute . The spikes (geometric sum). Total amortized .(b) Total → amortized NOT constant. ❌ (c) Total → amortized . ✅ (Being expensive every time is fine as long as the cost is a constant.)

What we did: summed each sequence and divided by . Why: amortized cost is literally total.

Recall Solution 1.2

False. A dynamic-array resize is genuinely , yet the sequence is still amortized . A rare spike is allowed — what matters is that the total stays . The whole point of amortization is to permit occasional expensive ops.


Level 2 — Application

Recall Solution 2.1

A resize triggers when the array is already full and we push one more.

Push Cap before Full? Copy Write Cost
1 1 no 0 1 1
2 1 yes → cap 2 1 1 2
3 2 yes → cap 4 2 1 3
4 4 no 0 1 1
5 4 yes → cap 8 4 1 5
6 8 no 0 1 1
7 8 no 0 1 1
8 8 no 0 1 1

Total . Amortized . ✅ Check with the formula: copies , writes , total . Matches.

Recall Solution 2.2

Using , the largest size allowed at capacity is . Rehash fires when the next insert would exceed it.

  • cap 4 allows size up to → insert 3rd: rehash to cap 8, copy 2.
  • cap 8 allows up to 4 → insert 5th: rehash to cap 16, copy 4.
  • cap 16 allows up to 8 → insert 9th: rehash to cap 32, copy 8.
  • cap 32 allows up to 16 → insert 17th: rehash to cap 64, copy 16.
  • cap 64 allows up to 32 → we stop at 20 items, no more rehash.

Capacities: . Total copy cost . Inserts done to go from 2 to 20 items writes. Total ; amortized . ✅


Level 3 — Analysis

Recall Solution 3.1

With chunk size , resizes happen at sizes Taking a multiple of for cleanliness (rounding changes nothing asymptotically — see the note below), there are exactly resizes, and the -th resize copies elements. For large this is . Amortized . ❌ On the rounding: for general the number of resizes is , so the true copy cost is . Since differs from by less than , this changes the sum by only — negligible against the leading term. The floor never rescues the quadratic. Why: this is an arithmetic series (), which grows quadratically. Making bigger only shrinks the constant — it never removes the . You need geometric growth to kill the square.

Recall Solution 3.2

Setup — how many resizes? Let the capacities reached be where , and is the last one needed to hold items. Because each capacity is at least times the previous, , so the last exponent satisfies and there are resizes. Precisely, since and we stop once capacity , the number of resizes is at most — the largest exponent with — which is exactly why . Keep that "" in mind; it becomes a harmless correction term.

Bounding each capacity (the rounding must be tracked carefully). The ceiling adds at most per step, and those errors compound, so we cannot claim . Instead unroll the recurrence : With the tail is itself a geometric sum , giving the honest bound So the accumulated rounding error is not a constant ; it is absorbed into the same factor — the capacities are at most a constant multiple of .

Summing the copy cost. The copy cost at capacity is , so Since we have , so That is already linear. A tighter bound recovers the clean constant: the dominant geometric sum contributes copy cost , and the accumulated rounding contributes only the smaller-order tail plus the resize count — both lower order after dividing by .

Divide by . The leading term gives amortized copy cost per push because every correction term (the compounded rounding, the resize count) is lower order and washes out in the limit. Adding the per write gives total amortized .

  • : copy part → total . Matches the parent note's "3." ✅
  • : copy part → total . Slower per-op, but still — and it wastes less memory.

The next figure plots this constant against the growth factor so you can see how the choice of trades speed against memory:

Figure — Amortized O(1) operations

Reading the curve (refer to the figure above): the cyan curve is . As (left edge) it shoots up toward infinity — this is the amber-annotated "approaching additive growth" bad case, exactly the disaster of Exercise 3.1. Moving right, the amber dot at sits at cost , and as grows further the cost sinks toward but memory waste (empty slots) rises. is the popular compromise: cheap enough, wastes at most half the table.


Level 4 — Synthesis

Recall Solution 4.1

Amortized cost , where . Summing over the run, total amortized total actual . For amortized costs to upper-bound the real total we need — so we must pin down and show never dips below it.

(a) Fixing the starting point. The empty-array corner does look bad: at size , capacity , . So the naive claim " always" is false at the very start — this is the subtlety the method demands we handle. Two standard, equivalent fixes:

  • Fix 1 (shift the baseline). Take as the reference value. All we need is that never goes below its initial value . Once the first element is added the array is full-or-fuller and forever after, so indeed for the whole run. The constant offset adds only to the total — it does not affect the amortized bound.
  • Fix 2 (start at capacity 1 already reserved, or define ). Then and throughout by construction.

Either way the prerequisite " at all times" holds, so the amortized costs below are a genuine upper bound. From the first non-empty state onward, "at least half full" means , i.e. , so . Right after a doubling the array is exactly half full (), and only rises as we add.

(b) Cheap push (room exists): actual (one write). Size , capacity unchanged → . Amortized . ✅

(c) Resize push: before, (array full). Actual cost copies write . After, size , capacity . . Amortized . ✅ The copy cost is exactly cancelled by the drop in potential — the "saved work" gets spent.

Both cases give exactly 3 → amortized , no averaging, no probability.

Recall Solution 4.2

Accounting idea. Each element can be popped at most once, and it must have been pushed before it can be popped. We use these two facts to design charges that never let the credit bank go negative.

The charging scheme.

  • Charge each push ₹2 (2 units): ₹1 pays for the actual push (the real work), and ₹1 is stored as credit on that specific element.
  • Charge each multipop ₹0 up front: when it removes an element, that element's pop is paid by the ₹1 of credit sitting on it.

Why the bank never goes negative (the key invariant, now proved). Define number of elements currently on the stack, since exactly ₹1 of credit is deposited on each element when it is pushed and that ₹1 is still there until the element is popped. This credit is always the stack's current size, which is by definition — you cannot have a negative number of items. Concretely:

  • A push adds one element and deposits its ₹1 → credit rises by 1, stays .
  • A multipop(k) removes elements, spending exactly units of credit — one from each removed element. Since it removes only elements that are present (each of which carries its own ₹1), it never spends credit that isn't there. Credit drops by but stays because .

So at every moment : the invariant holds, the accounting is honest.

The bound. Total money charged (₹2 per push) (₹0 per multipop) . Because the charges never undercharge (credit never went negative), the real total cost total charged . Amortized cost per operation , even though a single multipop can be .

Why the same trick works: like resizing, a big op (multipop) only removes work that was individually pre-paid by cheap ops (push). This is the exact same "pay it forward" as charging 3 units per array push.


Level 5 — Mastery

Recall Solution 5.1

Fill the array to exactly capacity (size ). Now alternate:

  • push → size = full → double to cap , copy . Cost .
  • pop → size back to halve to cap , copy . Cost .
  • push → full again → double, copy … and so on.

Every single operation triggers a full copy. Over such ops, total amortized . ❌ The structure oscillates across the resize boundary — it's stuck at the worst possible spot.

The fix — hysteresis: grow at load (double when full) but shrink only when size drops to capacity (halve then). This leaves a gap between the grow and shrink thresholds, so after any resize the array is exactly half full and needs more cheap ops before the next resize can fire. The geometric argument (and a potential like ) then restores amortized .

Recall Solution 5.2

From Exercise 3.2, amortized copy cost with : Total amortized per push . ✅ One-line reason: for any the copy sum is a convergent-ratio geometric series, so it's bounded by the finite constant times — the exponent stays 1, never 2. Only breaks it.

(Fun fact: since satisfies , dividing by gives , i.e. . Hence . So golden-ratio copy cost equals .)


Recall check

Amortized cost of a cost sequence ?
total cost over ops, divided by — a worst-case upper bound over the sequence, with no probability involved.
Growth factor gives amortized copy cost per push of
(so at , giving total ).
Why does symmetric grow-at-full / shrink-at-half fail?
alternating push/pop straddles the shared boundary, forcing an copy every op → total.
Fix for the shrinking trap
hysteresis — grow when full, shrink only at capacity, leaving a gap between thresholds.
Multipop stack sequence of ops costs
total (charge push 2 units, each element pays its own pop, credit = stack size never goes negative) → amortized .
Why must a potential function satisfy throughout?
otherwise the amortized costs stop being an upper bound on the real total — you'd be spending credit you never saved.

Connections

  • Amortized O(1) operations — the parent this page drills.
  • Dynamic Arrays — the resizing structure behind L2–L5.
  • Hash Tables · Load Factor — Exercise 2.2's rehash trigger.
  • Geometric Series — the engine in Exercises 3.1, 3.2, 5.2.
  • Potential Method — Exercise 4.1.
  • Stack with Multipop — Exercise 4.2.
  • Big-O Notation — the language of every answer.