Greedy Algorithms
Greedy algorithms make locally optimal choices at each step, hoping to find a global optimum.
What are Greedy Algorithms?
A greedy algorithm makes the locally optimal choice at each step, hoping that these local choices will lead to a globally optimal solution.
When to Use?
- When local optimal choices lead to global optimal solution
- When you need to find an approximate solution quickly
- Problems involving optimization
Common Applications
- Minimum Spanning Tree (Kruskal's, Prim's)
- Dijkstra's Shortest Path
- Huffman Coding
- Activity Selection
- Coin Change (with certain constraints)
Example Problems
1. Coin Change Problem
Problem Description
Given a set of coin denominations and a target amount, find the minimum number of coins needed to make that amount.
Example
Input: coins = [1, 5, 10, 25], amount = 67
Output: 6
Explanation: 25 + 25 + 10 + 5 + 1 + 1 = 67 (6 coins)
Solution
function minCoins(coins, amount) {
// Sort coins in descending order
coins.sort((a, b) => b - a);
let totalCoins = 0;
let remaining = amount;
let result = [];
for (let coin of coins) {
while (remaining >= coin) {
remaining -= coin;
totalCoins++;
result.push(coin);
}
}
return {
numberOfCoins: totalCoins,
coinsUsed: result,
amount: amount,
};
}
Time Complexity
- Time: O(n * amount) where n is the number of coin denominations
- Space: O(1)
2. Activity Selection Problem
Problem Description
Given a set of activities with start and finish times, find the maximum number of activities that can be performed by a single person, assuming they can only work on one activity at a time.
Example
Input:
start = [1, 3, 0, 5, 8, 5]
finish = [2, 4, 6, 7, 9, 9]
Output: 4
Explanation: Activities (1,2), (3,4), (5,7), (8,9) can be selected
Solution
function activitySelection(start, finish) {
// Create activities array with indices
let activities = start.map((s, i) => ({
index: i,
start: s,
finish: finish[i],
}));
// Sort by finish time
activities.sort((a, b) => a.finish - b.finish);
let selected = [activities[0]];
let lastSelected = activities[0];
for (let i = 1; i < activities.length; i++) {
if (activities[i].start >= lastSelected.finish) {
selected.push(activities[i]);
lastSelected = activities[i];
}
}
return {
count: selected.length,
activities: selected.map((a) => a.index),
};
}
Time Complexity
- Time: O(n log n) due to sorting
- Space: O(n)
Practice Problems
To master greedy algorithms, try solving these problems:
- Fractional Knapsack
- Job Sequencing with Deadlines
- Huffman Coding
- Minimum Platforms
- Maximum Meetings in One Room
Common Greedy Strategies
-
Sort First
- Sort input based on some criteria
- Make decisions in sorted order
-
Take Maximum/Minimum
- Choose the largest/smallest available option
- Useful in optimization problems
-
Interval Scheduling
- Sort by end time
- Select non-overlapping intervals
Tips for Solving Greedy Problems
- Verify that greedy approach works
- Identify the local optimal choice
- Prove correctness if possible
- Consider edge cases
- Look for sorting opportunities
When Greedy Might Fail
-
0/1 Knapsack Problem
- Greedy doesn't work because we can't take fractions
- Requires dynamic programming
-
Traveling Salesman Problem
- Local optimal choices don't lead to global optimum
- Requires other approaches