14.21 Complexity Analysis

Greedy algorithms often have simple control flow.

14.21 Complexity Analysis

Greedy algorithms often have simple control flow. Sort the input, scan once, push into a heap, pop from a heap, or union two components. Because the code is usually compact, it is tempting to assume the complexity is obvious.

That assumption is risky.

The cost of a greedy algorithm is usually hidden in the supporting operation:

Sort
Heap push
Heap pop
Find
Union
Tree lookup
Stack update

This recipe shows how to analyze greedy algorithms by identifying the dominant operation and counting how often it occurs.

Problem

Given a greedy algorithm, determine its time and space complexity accurately.

The main questions are:

  1. Does the algorithm sort?
  2. Does it scan once or many times?
  3. Does it use a heap?
  4. Does it use union-find?
  5. Does it maintain an ordered structure?
  6. Does each item enter and leave the structure once?
  7. Does the implementation accidentally rescan data?

The Basic Pattern

Most greedy algorithms have this shape:

Preprocess input

Initialize state

For each item:
    inspect item
    update state
    maybe insert into structure
    maybe remove from structure

Return answer

The complexity is the sum of these pieces.

A typical greedy algorithm is not expensive because it makes difficult decisions. It is expensive because it must maintain the data needed to make those decisions efficiently.

Sorting-Dominated Greedy Algorithms

Many greedy algorithms start by sorting.

Examples:

Algorithm Sort Key Scan
Interval scheduling Finish time Select compatible intervals
Fractional knapsack Density Take capacity
Activity selection Finish time Select activities
Merge intervals Start time Extend ranges
Rescue boats Weight Move two pointers

Sorting costs:

O(n log n)

The scan usually costs:

O(n)

Total:

O(n log n)

Space depends on the sorting implementation and output storage.

Example: Interval Scheduling

Algorithm:

Sort intervals by finish time.
Scan once.
Accept interval if compatible.

Cost:

Step Cost
Sort intervals O(n log n)
Scan intervals O(n)
Store result O(k)

Total time:

O(n log n)

Space:

O(k)

for the result, where k is the number of selected intervals.

If the result is only counted, auxiliary space is:

O(1)

excluding sort internals.

Heap-Dominated Greedy Algorithms

Priority queues appear when candidates become available dynamically.

Examples:

Algorithm Heap Type Operation
Huffman coding Min-heap Extract two smallest
Minimum refueling stops Max-heap Extract largest fuel
Course scheduling Max-heap Remove longest job
Meeting rooms Min-heap Reuse earliest room
Dijkstra Min-heap Extract closest vertex
Prim Min-heap Extract cheapest edge

Each heap operation costs:

O(log n)

If each item is pushed and popped at most once, the total heap cost is:

O(n log n)

Example: Minimum Refueling Stops

Each station is inserted into the heap once.

Each selected station is popped at most once.

Step Count Cost
Push station n O(log n)
Pop station at most n O(log n)

Total:

O(n log n)

Space:

O(n)

for the heap.

The key observation is not the loop count alone. The loop may contain nested while statements, but every station advances through the algorithm once.

Nested Loops Can Still Be Linear

Many greedy algorithms contain nested loops but remain linear after sorting.

Example:

for reach < target:
    while next station is reachable:
        push station
    pop best station

This looks nested.

But the inner loop advances a station index that never moves backward.

Each station is processed once.

Therefore the total number of inner-loop iterations over the whole algorithm is n, not n per outer iteration.

This is amortized reasoning.

Union-Find Greedy Algorithms

Kruskal's algorithm is the standard example.

Steps:

  1. Sort edges by weight.
  2. For each edge, check whether its endpoints are already connected.
  3. Union components when the edge is accepted.

Sorting:

O(E log E)

Union-find operations:

O(E α(V))

where α is the inverse Ackermann function, effectively constant for practical input sizes.

Total:

O(E log E)

because sorting dominates.

Ordered-Set Greedy Algorithms

Some greedy algorithms require operations such as:

Find smallest value >= x
Find largest value <= x
Delete value
Insert value

These are not heap operations.

They require a balanced search tree or equivalent ordered structure.

Each operation typically costs:

O(log n)

If performed once per item:

O(n log n)

Examples include resource assignment, interval allocation, and matching problems with ordered capacities.

Stack-Based Greedy Algorithms

Monotonic stack algorithms often run in linear time.

Examples:

  • Remove duplicate letters.
  • Remove k digits.
  • Next greater element.
  • Largest rectangle in histogram.

The key fact is:

Each element is pushed once and popped at most once.

Therefore total stack operations are:

O(n)

Even if the code contains a while loop inside a for loop, total popping is bounded by the number of pushes.

Example: Remove k Digits

For each digit:
    while stack top is larger and k > 0:
        pop
    push digit

Each digit is pushed once.

Each digit can be popped once.

Total time:

O(n)

Space:

O(n)

for the stack.

Two-Pointer Greedy Algorithms

Two-pointer greedy algorithms are also typically linear after sorting.

Example:

left = 0
right = n - 1

while left <= right:
    decide
    move left, right, or both

Each pointer moves in only one direction.

Total pointer movements:

O(n)

If sorting is required:

O(n log n)

If input is already sorted:

O(n)

Example: Rescue Boats

Sorting weights:

O(n log n)

Pointer scan:

O(n)

Total:

O(n log n)

Space:

O(1)

excluding sort internals.

Complexity Table

Pattern Typical Time Typical Space
Sort + scan O(n log n) O(1) to O(n)
Sort + two pointers O(n log n) O(1)
Heap greedy O(n log n) O(n)
Union-find after sorting O(E log E) O(V)
Monotonic stack O(n) O(n)
Ordered tree O(n log n) O(n)
Frequency array O(n + σ) O(σ)

Here σ is the alphabet or key-space size.

Space Complexity

Space analysis should distinguish between:

  • Input storage
  • Output storage
  • Auxiliary storage
  • Sorting workspace
  • Data structure storage

For example, interval scheduling may store selected intervals.

If the output contains k intervals, output space is:

O(k)

Auxiliary space may still be:

O(1)

Similarly, Huffman coding stores tree nodes.

For n symbols, the final tree contains:

2n - 1

nodes.

Thus space is:

O(n)

Beware of Hidden Copies

In languages such as Go, implementation details matter.

This code may copy data:

result = append(result, current)

That is fine for small structs.

But if the item is large, storing pointers may be better.

Similarly, sorting in place mutates input:

sort.Slice(items, ...)

If the caller expects the original order preserved, copy first:

items = append([]Item(nil), items...)

That copy costs:

O(n)

space and time.

Numeric Costs

Greedy algorithms often compare values such as:

value / weight

Be careful with floating-point density.

Instead of comparing:

a.Value/a.Weight > b.Value/b.Weight

prefer cross multiplication when values are integers:

a.Value*b.Weight > b.Value*a.Weight

This avoids precision errors.

However, cross multiplication can overflow for large values.

In that case, use wider integer types or arbitrary precision arithmetic.

Complexity analysis should include numeric model assumptions when values may be large.

When Greedy Is Linear

A greedy algorithm can be linear when:

  • Input is already ordered.
  • The key space is bounded.
  • A stack or queue suffices.
  • Frequency arrays replace maps or heaps.
  • Each element is processed a constant number of times.

Examples:

Problem Reason
Remove k digits Each digit pushed and popped once
Partition labels Fixed alphabet last-occurrence array
Subsequence matching Single scan
Already sorted interval scan No sorting needed
Counting-based greedy Bounded key space

The absence of sorting or logarithmic operations usually indicates possible linear time.

When Greedy Becomes Quadratic

Greedy implementations often become quadratic by accident.

Common causes:

Repeated Scanning

For each decision:
    scan all candidates

Rebuilding Heaps

For each step:
    build priority queue again

Removing from the Front of Arrays

In Go, repeatedly doing:

a = a[1:]

may keep references to the underlying array longer than intended.

In some languages, front removal itself is linear.

Checking Feasibility Expensively

For graph algorithms, checking whether an edge creates a cycle by running DFS each time is much slower than union-find.

Amortized Analysis

Many efficient greedy algorithms rely on amortized bounds.

The argument is:

Even though one iteration may do many operations,
each element participates in those operations only a bounded number of times.

Examples:

Algorithm Amortized Reason
Monotonic stack Each element popped once
Refueling stops Each station pushed once
Sliding heap patterns Each event inserted once
Two pointers Each pointer moves one direction
Union-find Path compression reduces future cost

Amortized analysis is often the cleanest way to explain greedy performance.

Proof and Complexity Are Separate

Correctness and complexity answer different questions.

Correctness asks:

Does this greedy rule produce an optimal solution?

Complexity asks:

How much work does the implementation perform?

A greedy algorithm may be correct but slow.

A greedy algorithm may be fast but incorrect.

Both analyses are required.

Takeaway

Complexity analysis for greedy algorithms usually reduces to identifying the dominant supporting operation. Sorting gives O(n log n), heaps give logarithmic insertion and removal, union-find makes connectivity checks effectively constant, stacks and two pointers often give linear scans, and ordered trees support successor queries in logarithmic time. Nested loops do not automatically imply quadratic behavior; count how many times each item enters, leaves, or moves through the data structure. A precise greedy analysis separates the proof of optimality from the cost of maintaining the state needed to make each greedy choice.