Hello! Welcome to the final lesson in our module on complexity analysis.
In our previous lessons, we've focused on worst-case analysis of individual algorithms, using tools like Big-O notation, recurrence relations, and the Master Theorem. This approach is fundamental, but sometimes it can be overly pessimistic. It tells us the cost of the most expensive single operation, but doesn't capture the full picture when operations have varying costs within a sequence.
Today, we address this by exploring a different perspective. The learning outcome is to explain amortized analysis using the aggregate and accounting methods. This technique allows us to determine the average cost per operation over a sequence in the worst case. For you, this is a key concept that mathematically explains the high performance of common data structures you've architected systems with, like Java's ArrayList or Python's dynamic lists, even when they occasionally perform very expensive operations like resizing.
1. The Limits of Single-Operation Worst-Case Analysis
Let's consider a familiar data structure: a dynamic array (like std::vector or ArrayList). It provides an append operation that adds an element to the end.
- The "cheap" case: If the underlying array has space, this is an
O(1)operation. You just place the element at the next index. - The "expensive" case: If the array is full, the data structure must perform a costly resize:
- Allocate a new, larger array (typically double the size).
- Copy every element from the old array to the new one.
- Add the new element.
This single operation takesO(n)time, wherenis the current number of elements.
A strict worst-case analysis would conclude that the append operation is O(n). If we need to perform m appends, does this mean the total time is O(m \cdot n)? This feels incorrect, and it is. The expensive O(n) cost happens only rarely. Amortized analysis provides a formal way to prove that the average cost is, in fact, much lower.
2. The Aggregate Method: A Total Cost Perspective
The aggregate method is the most straightforward approach to amortized analysis. The process is simple:
- Determine the total worst-case time, , for a sequence of operations.
- The amortized cost per operation is then calculated as .
Let's apply this to the multi-pop stack example, a classic introductory problem. A stack supports PUSH (cost 1), POP (cost 1), and MULTIPOP(k) (cost k). A naive analysis would say a sequence of n operations could cost O(n^2) if all were MULTIPOP(n). However, this is impossible.
To see how to derive a tighter bound, let's watch a clear explanation.
Amortized Analysis: Aggregate, Accounting and Potential Method (with examples)
This first video from CS Simplified provides an excellent, step-by-step application of the aggregate method to the multi-pop stack. It clearly demonstrates the core insight: an element can only be popped once, regardless of whether it's via POP or MULTIPOP.
Watch the segment from the beginning until 11:09. Focus on the logical argument that the total number of pops (from all operations combined) is bounded by the total number of pushes. This insight is what allows for a tight bound on the total cost.
As the video showed, for any sequence of operations on an initially empty stack:
- The total number of
PUSHoperations is at most . Total cost for pushes isO(n). - An element must be pushed onto the stack before it can be popped.
- Therefore, the total number of items popped (summed across all
POPandMULTIPOPcalls) cannot exceed the total number of items pushed. This is at most . - The total cost for all
POPandMULTIPOPoperations combined is thereforeO(n).
So, the total cost for any sequence of operations is O(n) + O(n) = O(n).
The amortized cost per operation is O(n) / n = O(1).
This is a powerful result: despite some operations being potentially expensive, the average cost over any sequence is constant.
3. The Accounting Method: Pre-paying for Future Work
The accounting method provides a different, often more intuitive, lens. We use a financial analogy:
- We assign a specific amortized cost (a "payment" or "charge") to each type of operation. This is our analytical cost, not the actual cost.
- When an operation's amortized cost is higher than its actual cost, we "overpay" and store the difference as credit.
- When an operation's amortized cost is lower than its actual cost, we use the stored credit to pay for it.
- The Golden Rule: The total credit stored in the data structure must never become negative.

Let's see how this works with our two main examples: the multi-pop stack and the dynamic array.
Amortized Analysis: Aggregate Analysis and Accounting Method
The following video from Algorithms Lab explains the accounting method using a 'coin' analogy. It applies the method to the multi-pop stack, a binary counter, and the dynamic array, providing three clear applications of the same core principle.
Please watch two segments from this video: Multi-pop Stack (08:56 - 17:02): See how assigning an amortized cost of 2 to PUSH allows us to 'save' a coin with each element, which then pays for its eventual removal by POP or MULTIPOP (amortized cost 0). Dynamic Array (29:06 - 37:00): Observe how assigning an amortized cost of 3 to append lets us save up enough credit over time to pay for the expensive O(n) resize operation when it occurs. Focus on how the 'invariant' (credit ≥ 0) is maintained in each case.
Let's formalize the logic from the video:
Accounting for the Multi-pop Stack:
- Amortized Costs:
PUSH= 2,POP= 0,MULTIPOP= 0. - Actual Costs:
PUSH= 1,POP= 1,MULTIPOP(k)=k. - Analysis:
- When we
PUSHan element, we pay 2 coins. 1 coin pays the actual cost of the push. The other coin is stored with the element on the stack. - When we
POPorMULTIPOPan element, its actual cost of 1 is paid for by the coin stored with it. - The total credit is the number of items on the stack, which is always . The rule holds.
- Since the highest amortized cost is 2, the amortized cost per operation is
O(1).
- When we
Accounting for the Dynamic Array:
- Amortized Cost:
append= 3. - Actual Costs:
appendwithout resize = 1.appendwith resize =n + 1(to copynold items and add the new one). - Analysis:
- When we
appendan element, we pay 3 coins. - 1 coin pays the actual cost of insertion.
- 2 coins are stored as credit.
- Let's consider an array that just resized from size
mto2m. It currently holdsmelements. The nextminsertions will fill the array. - During these
minsertions, we collectm * 2 = 2mcoins of credit. - When the
(2m+1)-th element is added, a resize is triggered. The array has2melements to copy. The cost is2m. - The
2mcoins of credit we saved are used to pay for this copy. The system is solvent. - The amortized cost is a constant 3, so the operation is
O(1).
- When we
Test your understanding!
Consider a k-bit binary counter that starts at 0. The only operation is increment, which costs 1 unit for every bit that is flipped. For example, incrementing from 3 (011) to 4 (100) costs 3 flips.
A naive worst-case analysis says an increment could cost O(k). Using either the aggregate or accounting method, can you show that the amortized cost of an increment operation is O(1)?
(Hint for aggregate: How many times does bit 0 flip in n increments? How many times does bit 1 flip? Bit i?)
(Hint for accounting: When a bit flips from 0 to 1, what if you "pre-pay" for its future flip back to 0?)
Show answer
Here's how both methods solve this:
1. Aggregate Method:
In a sequence of n increments:
- Bit 0 (the rightmost bit) flips on every increment. Total:
nflips. - Bit 1 flips on every 2nd increment. Total:
⌊n/2⌋flips. - Bit
iflips on every2^i-th increment. Total:⌊n/2^i⌋flips.
The total number of flips is the sum over all bits:
The total cost for n increments is O(n). Therefore, the amortized cost per increment is O(n)/n = O(1).
2. Accounting Method:
- Set the amortized cost of an
incrementto 2 coins. - Whenever a bit flips from 0 to 1, use 1 coin to pay for the flip, and store the 2nd coin as credit on that bit.
- Whenever a bit flips from 1 to 0, use the credit stored on that bit to pay for the flip.
- An
incrementoperation involves flipping a sequence of 1s to 0s, and then flipping exactly one 0 to a 1. - Each 1-to-0 flip is paid for by the credit already on the bit.
- The single 0-to-1 flip is paid for by one of the coins from our amortized cost. The second coin is then stored on this newly set bit.
- The total credit in the system equals the number of 1s in the counter, which is always . The invariant holds.
- The amortized cost is a constant 2, so the operation is
O(1).
You can find detailed video walkthroughs for this problem in the CS Simplified video (LINK) at timestamp 11:09 (Aggregate) and 26:39 (Accounting).
Conclusion
This lesson concludes our foundational module on complexity analysis. We've moved from simple loop counting to analyzing complex recursive algorithms, and now to understanding the performance of sequences of operations.
Key Takeaways:
- Amortized analysis provides a more practical measure of performance for data structures where some operations are occasionally expensive. It gives the worst-case average cost over a sequence.
- The Aggregate Method calculates the total cost of a sequence of
noperations and divides bynto find the amortized cost. It's conceptually simple but requires analyzing the entire sequence at once. - The Accounting Method uses a financial analogy of "pre-paying" for expensive operations. It assigns amortized costs to individual operation types and uses "credit" to balance payments, ensuring the system never goes into "debt".
Next Lesson Preview:
We are now ready to dive into the core data structures and algorithms that form the bedrock of coding interviews. In our next module, "Core Data Structures: Arrays, Strings & Hash Maps," we will begin by learning how to apply the two-pointer technique to solve problems on sorted arrays in O(n) time. This is a classic interview pattern that relies on the kind of efficiency thinking we've been developing.