Skip to Content
Course content

409: Greedy Algorithm Patterns in Python

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this happen constantly in technical interviews and architectural reviews: a developer suggests a "greedy" approach because it's intuitive, assuming that if they just pick the best-looking option at every single step, they'll end up with the best possible overall result. It feels right, it's easy to code, and it's fast. But here is the danger: "intuitive" does not mean "mathematically correct."

The "Always Optimal" Fallacy

The biggest misconception is that greedy algorithms are a universal shortcut to the global optimum. They aren't. Let me show you why using a classic coin-change scenario. Imagine you're writing a function to give a customer the minimum number of coins for a specific amount. Your available coin denominations are 1, 3, and 4 cents.

If you need to make 6 cents in change, a greedy algorithm says: "Take the largest coin possible first."

# Greedy Logic for 6 cents:
# 1. Take 4 cents (Remaining: 2)
# 2. Take 1 cent  (Remaining: 1)
# 3. Take 1 cent  (Remaining: 0)
# Total coins: 3

But wait. If you had just taken two 3-cent coins, you'd be done in two moves. The greedy approach failed because it was too short-sighted; it grabbed the 4-cent coin and locked itself out of the more efficient 3+3 combination. In this case, you'd actually need Dynamic Programming to guarantee the best answer. I've seen people waste hours debugging "bugs" in their code that were actually just fundamental flaws in their choice of algorithm.

The Greedy Choice Property

So, when can you actually use this pattern? A greedy algorithm works only if the problem possesses the "Greedy Choice Property." This is a fancy way of saying that making a locally optimal choice right now will never prevent you from reaching the globally optimal solution later.

Think of it like walking down a mountain in thick fog. If the mountain is a perfect cone, always stepping in the steepest downward direction will eventually get you to the bottom. But if the mountain has "false bottoms" (local minima), the greedy approach will leave you stranded in a valley halfway up the peak.

The Activity Selection Pattern

One of the most reliable places to use a greedy pattern in Python is for interval scheduling. Suppose you have a list of tasks with start and end times, and you can only do one task at a time. You want to fit as many tasks as possible into your day.

The "greedy" trick here isn't to pick the shortest task or the one that starts earliest. Those both fail in certain edge cases. The winning strategy is to always pick the task that finishes earliest. By doing this, you leave as much room as possible for everything else that follows.

def schedule_tasks(tasks):
    # tasks is a list of (start, end) tuples
    # The magic happens here: sort by the END time
    sorted_tasks = sorted(tasks, key=lambda x: x[1])
    
    selected_tasks = []
    last_end_time = 0
    
    for start, end in sorted_tasks:
        if start >= last_end_time:
            selected_tasks.append((start, end))
            last_end_time = end
            
    return selected_tasks

# Example:
my_tasks = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11)]
print(schedule_tasks(my_tasks)) 
# Output: [(1, 4), (5, 7), (8, 11)]

Notice how simple that is. We sort once, loop once, and we're done. The complexity is $O(N \log N)$ due to the sort, which is incredibly efficient compared to exploring every possible combination of tasks.




📋 Practical Task

Build a Fractional Knapsack Optimizer

Unlike the 0/1 Knapsack problem (where you must take an item or leave it), the Fractional Knapsack problem allows you to take a fraction of an item (like grains of gold or liquid). This makes it a perfect candidate for a greedy algorithm.

Your Goal: Write a function get_max_value(capacity, items).

  • capacity: An integer representing the maximum weight the bag can hold.
  • items: A list of tuples, where each tuple is (value, weight).

To solve this, you need to calculate the value-per-unit-weight for each item, sort them in descending order, and fill your bag. If the bag can't fit the whole next item, take the largest fraction that will fit.

Test your code with this scenario:

capacity = 50
items = [(60, 10), (100, 20), (120, 30)] 
# Expected result: 240.0 
# (Full 10kg item, full 20kg item, and 20kg of the 30kg item)
Rating
0 0

There are no comments for now.

to be the first to leave a comment.