Greedy Algorithm Introduction
- Greedy makes the locally optimal choice at each step — no backtracking.
- Greedy is correct when the problem has the greedy choice property: local optimal = global optimal.
- Always prove correctness before trusting a greedy solution — or test with edge cases.
A greedy algorithm makes the locally optimal choice at each step, hoping it leads to a globally optimal solution. Greedy works when the problem has the greedy choice property: a locally optimal choice is also globally optimal. Classic examples include interval scheduling, Dijkstra's algorithm, and Huffman coding. Coin change with arbitrary denominations may require dynamic programming instead.
Interval Scheduling — The Power of the Earliest Finish Time
The Interval Scheduling problem asks: 'Given a set of activities with start and end times, what is the maximum number of non-overlapping activities you can perform?'
Many beginners try to pick the shortest interval or the one that starts earliest, but those fail. The proven greedy choice is to always pick the activity that ends earliest. This leaves the maximum amount of time remaining for all subsequent activities.
package io.thecodeforge.algorithms.greedy; import java.util.*; /** * Production-grade implementation of the Activity Selection Problem. * Time Complexity: O(N log N) due to sorting. */ public class IntervalScheduler { public static class Interval { int start, end; public Interval(int start, int end) { this.start = start; this.end = end; } @Override public String toString() { return "(" + start + ", " + end + ")"; } } public List<Interval> selectMaxActivities(List<Interval> intervals) { if (intervals == null || intervals.isEmpty()) return Collections.emptyList(); // Greedy Choice: Sort by finish time intervals.sort(Comparator.comparingInt(a -> a.end)); List<Interval> result = new ArrayList<>(); Interval lastSelected = intervals.get(0); result.add(lastSelected); for (int i = 1; i < intervals.size(); i++) { Interval current = intervals.get(i); // If this activity starts after or when the last one ends, pick it if (current.start >= lastSelected.end) { result.add(current); lastSelected = current; } } return result; } public static void main(String[] args) { IntervalScheduler scheduler = new IntervalScheduler(); List<Interval> input = Arrays.asList( new Interval(1, 4), new Interval(3, 5), new Interval(0, 6), new Interval(5, 7), new Interval(3, 8), new Interval(5, 9), new Interval(6, 10), new Interval(8, 11), new Interval(12, 16) ); System.out.println("Selected intervals: " + scheduler.selectMaxActivities(new ArrayList<>(input))); } }
Coin Change — Understanding the Greedy Failure
For a 'canonical' coin system (like USD: 1, 5, 10, 25), the greedy approach of taking the largest coin first is mathematically optimal. However, if a country used coins of 1, 3, and 4 units, greedy would fail to find the best way to make 6 units. Greedy would take a 4, then two 1s (3 coins), whereas the optimal is two 3s (2 coins).
-- io.thecodeforge.analytics -- Tracking greedy vs optimal counts in a simulated environment CREATE TABLE denominations (coin_val INT); INSERT INTO denominations VALUES (1), (3), (4); -- A simple query to show how many of the LARGEST coin greedy would take -- for an amount of 6. SELECT 6 AS target_amount, MAX(coin_val) AS greedy_first_pick, (6 / MAX(coin_val)) AS coins_taken, (6 % MAX(coin_val)) AS remainder_to_process FROM denominations WHERE coin_val <= 6; -- This logic clearly shows greedy leaves a remainder of 2, requiring 2 more coins.
Environment Setup for Algorithm Analysis
To benchmark greedy algorithms against complex DP solutions, we use a containerized environment to ensure consistent CPU/Memory constraints for timing.
# io.thecodeforge.infrastructure FROM eclipse-temurin:17-jdk-jammy WORKDIR /app COPY . /app RUN javac io/thecodeforge/algorithms/greedy/IntervalScheduler.java # Run with memory limits to test efficiency CMD ["java", "-Xmx128m", "io.thecodeforge.algorithms.greedy.IntervalScheduler"]
🎯 Key Takeaways
- Greedy makes the locally optimal choice at each step — no backtracking.
- Greedy is correct when the problem has the greedy choice property: local optimal = global optimal.
- Always prove correctness before trusting a greedy solution — or test with edge cases.
- Interval scheduling (sort by end time) and Dijkstra are classic correct greedy algorithms.
- Coin change with arbitrary denominations requires dynamic programming, not greedy.
Interview Questions on This Topic
- QWhat is the 'Greedy Choice Property' and how does it differ from 'Optimal Substructure'?
- QLeetCode Standard: Explain how the 'Jump Game' can be solved using a greedy approach. What is the time complexity?
- QWhy does the greedy approach for the 0/1 Knapsack problem fail while it works for the Fractional Knapsack?
- QProve why sorting by 'Earliest Start Time' is a sub-optimal greedy strategy for the Interval Scheduling problem.
- QIn Huffman Coding, what is the greedy choice being made at each step of the tree construction?
Frequently Asked Questions
How do you prove a greedy algorithm is correct?
There are two standard proof methods. First is the 'Exchange Argument': you assume there is an optimal solution different from yours and prove that you can swap its elements with your greedy choices without making it worse. Second is 'Greedy Stays Ahead': you prove that at every step $k$, the greedy solution has progressed further toward the goal than any other possible algorithm.
What is the difference between greedy and dynamic programming?
The core difference is 'commitment.' A Greedy algorithm commits to a choice and never looks back. Dynamic Programming (DP) is more cautious; it calculates the results of all possible choices, stores them (memoization), and then picks the best path. Greedy is faster ($O(N \log N)$ or $O(N)$), while DP is more robust but more expensive ($O(N^2)$ or $O(N imes W)$).
When should I definitely NOT use a Greedy algorithm?
If your current choice significantly limits or dictates your future options in a way that might be suboptimal, Greedy is likely to fail. A classic example is the Fractional Knapsack vs. 0/1 Knapsack. Greedy works for Fractional (you can take bits of items), but fails for 0/1 because taking a heavy, semi-valuable item might prevent you from taking two light, very valuable items later.
Is Dijkstra's algorithm greedy?
Yes. Dijkstra is a greedy algorithm because at each step it picks the 'closest' unvisited vertex to the source, trusting that this local shortest path will contribute to the global shortest path. This is why it fails with negative edge weights—the greedy property is broken.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.