Greedy algorithms

A greedy algorithm builds an answer step by step, each time taking the choice that looks best right now and never taking it back. It is usually a sort and one pass, so its time complexity is O(n \log n), but it gives the right answer only for some problems. When a choice can turn out wrong later, use dynamic programming instead.

Explanation

A cashier gives 18 in change with coins of 1, 2, 5 and 10. The natural way is to take the largest coin that still fits: 10, then 5, then 2, then 1. That is 4 coins, the fewest possible.

The amount 18, then the four coins taken to pay it, from left to right: 10, 5, 2 and 1.

Fig. 1. The largest coin that fits, each time: 4 coins for 18.

This is a greedy algorithm. The same rule can fail. With coins of 1, 3 and 4, pay 6. The largest coin first gives 4 + 1 + 1, three coins, but 3 + 3 is only two.

Two rows of coins paying 6: the top row in red holds coins 4, 1 and 1, three coins, taken largest first; the bottom row in blue holds coins 3 and 3, the best payment with two coins. The number of coins is written to the left of each row.

Fig. 2. Paying 6 with coins 1, 3 and 4.

For coins like 1, 3 and 4, coin change tries every possible last coin instead.

Here is a problem where the right rule is less obvious. A meeting room gets 7 talks for one day, each with a start and an end hour. Take as many talks as possible so that no two overlap. A talk may start at the moment the previous one ends.

Timeline from 9 to 21 hours with seven talks as horizontal bars, one per row: talk 1 from 9 to 17, talk 2 from 10 to 13, talk 3 from 12 to 14, talk 4 from 13 to 16, talk 5 from 16 to 19, talk 6 from 17 to 20, talk 7 from 19 to 21.

Fig. 3. Seven talks on one day. Each bar runs from the talk's start to its end.

Trying every set of talks means 128 sets for 7 talks and 2^n for talks (brute force), too many for large . A greedy way is to take one talk, skip every talk that overlaps it, and repeat. Which talk do we take first?

The first try is starts first: the talk that starts earliest, talk 1, from 9 to 17. After it the rule takes talk 6, from 17 to 20, and talk 7 then overlaps it: 2 talks. Yet talks 2, 4, 5 and 7 fit together: the best schedule has 4 talks.

Timeline from 9 to 21 with seven talks as horizontal bars, one per row; talk 1 (9 to 17) and talk 6 (17 to 20) are red, the other five are grey.

Fig. 4. Starts first: 2 talks (red).

Short talks leave more room, so the next try is shortest first. Talks 3 and 7 both last 2 hours; take talk 3, from 12 to 14. But it overlaps talks 2 and 4, and both are in the best schedule. After talk 3 the rule takes talks 7 and 5: 3 talks.

Timeline from 9 to 21 with seven talks as horizontal bars, one per row; talk 3 (12 to 14), talk 5 (16 to 19) and talk 7 (19 to 21) are red, the other four are grey.

Fig. 5. Shortest first: 3 talks (red).

Once a talk is taken, the talks after it depend only on when it ends. The talk that ends first frees the room as early as possible, so it leaves the most time for the rest. In practice, sort the talks by end time and go through them once. This rule, ends first, is the greedy rule for this problem.

A timeline of the 7 talks from 9 to 21 with the sorted order 2, 3, 4, 1, 5, 6, 7 in cells below; step by step, ends first takes talks 2, 4, 5 and 7 (green), skips talks 3, 1 and 6 (grey, with the part that starts before the room is free struck through), and a dashed line moves to 13, 16, 19 and 21 as the room becomes free.

The cells show the talks sorted by end time: 2, 3, 4, 1, 5, 6, 7. Ends first goes through them in this order.

1/8

Fig. 6. Step through ends first with the buttons or the arrow keys. Green talks are taken, grey talks are skipped, and the dashed line marks the moment the room is free. The cells below show the order after sorting.

The run takes talks 2, 4, 5 and 7. After the sort, each talk is looked at once, so the work is the sort, O(n \log n) (time complexity).

In C++

Each use below has the same shape: sort by the rule, then take or skip each item in one pass.

Most talks

Each talk is a pair, sorted with a plain sort. In the pass, s and e are a talk's start and end, and freeFrom is the moment the room is free.

The input is , then lines "start end". For the 7 talks it is:

7
9 17
10 13
12 14
13 16
16 19
17 20
19 21
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<pair<int, int>> talks(n);   // {end, start}
    for (auto& [e, s] : talks) cin >> s >> e;

    // sort by the rule: ends first
    sort(talks.begin(), talks.end());

    // take or skip
    int count = 0;
    int freeFrom = 0;   // the room is free from this moment; times are not negative
    for (auto [e, s] : talks) {
        if (s >= freeFrom) {
            count++;
            freeFrom = e;
        }
    }
    cout << count << '\n';
}

Fewest coins

For coins 1, 2, 5 and 10, go through the coins from the largest and take as many of each as fit:

int count = 0;
for (int c : {10, 5, 2, 1}) {
    count += x / c;   // as many coins c as fit into x
    x %= c;           // what is left to pay
}

Other coin sets need their own check: compare largest first with brute force on small amounts, or use coin change.

Pans and lids

Each pan needs a lid at least as large as itself; cover as many pans as possible. Sort both, and give each pan, smallest first, the smallest free lid that fits. The index j points to the smallest unused lid.

sort(pans.begin(), pans.end());
sort(lids.begin(), lids.end());
int covered = 0, j = 0;
for (int p : pans) {
    while (j < (int)lids.size() && lids[j] < p) j++;   // skip lids too small for this pan
    if (j == (int)lids.size()) break;                  // no lids left
    covered++;
    j++;                                               // this lid is used
}
Sorted pans 3, 5, 6, 8 in a row above sorted lids 2, 4, 5, 6, 9; green arrows join pan 3 to lid 4, 5 to 5, 6 to 6 and 8 to 9, and lid 2 on the left is grey and crossed out.

Fig. 7. Each pan, smallest first, takes the smallest free lid that fits. Lid 2 fits no pan.

A lid too small for this pan is too small for every larger pan, so skipping it loses nothing. Giving a pan the smallest lid that fits keeps the larger lids for the larger pans. Pans and lids takes O(n \log n) for the two sorts. Both programs use memory.

Correctness proof

To prove the rule, we show that ends first never loses: some best schedule starts with the talk that ends first.

Take any best schedule. Its first talk ends no earlier than the greedy's first talk, which ends earliest of all. Replace it with the greedy's talk. The other talks of the schedule start after the replaced talk ended, so they also start after the greedy's talk ends. Nothing overlaps, and the count is the same. The swap needs only that the schedule has no overlaps, so it also works on talks 3, 5 and 7, which is not best: talk 3 ends at 14 and talk 2 at 13, so talks 2, 5 and 7 still fit.

Timeline from 9 to 21 hours in two rows. Top row: the schedule of talks 3 (12 to 14), 5 (16 to 19) and 7 (19 to 21) in blue, with talk 2 (10 to 13) in green just above talk 3. Bottom row: the same schedule with talk 3 replaced by talk 2, followed by talks 5 and 7. A dashed vertical line at 13 shows that talk 2 ends before talk 3 does, and before talk 5 starts.

Fig. 8. Talk 2 (green) ends first, so it can replace talk 3. The rest still fits.

Now skip every talk that starts before the greedy's talk ends, and the same argument applies to the talks that are left. Here talk 4, from 13 to 16, now fits between talks 2 and 5. So the greedy takes as many talks as a best schedule.

This proof is called an exchange argument, the usual way to prove a greedy rule: show that any best answer can be changed, one exchange at a time, into the greedy's answer without getting worse. For coins 1, 3 and 4 it fails: the best payment of 6, 3 + 3, has no 4, and no exchange puts a 4 into it without adding coins.

Common mistakes

  • Trusting a rule because it passes the sample. Shortest first takes all 4 films that fit on the festival above, but only 3 of the 4 talks. Compare a rule with brute force on small random inputs, or prove it with an exchange argument.

  • Taking the largest coin first for any coins. It can fail to pay at all: for coins 2 and 5 it pays 8 as 5 + 2 and is left with 1, though 2 + 2 + 2 + 2 works. Use coin change.

  • Using >= when a talk must start strictly after the previous one ends. On the films, >= takes A, C, E and F, but then at most 2 fit, such as A and E. Use s > freeFrom there.

  • Taking the best value per kilogram first in a knapsack. With room for 10 kg and unlimited items of 5 kg worth 10 and 6 kg worth 19, the best ratio gets 19 with one 6 kg item, but two 5 kg items give 20. Use knapsack DP.

Similar techniques

Brute force tries every answer: use it when is small.

Knapsack with items that can be cut (the fractional knapsack) is solved by the best value per kilogram first.

Sweep line keeps a running state over intervals in time order, such as how many overlap.

Two pointers moves through two sorted arrays, as in pans and lids.

Kruskal's, Prim's and Dijkstra's algorithms are greedy algorithms on graphs, each with its own proof.

Practice

Was this page helpful?