Greedy is about making the best choice at each step without looking back. Imagine you’re collecting coins scattered on a path—you pick up the highest value coin at each position. Sometimes this works perfectly; sometimes it fails spectacularly.
Pattern Recognition Signals:
“Minimum number of operations/items”
“Maximum value/count possible”
Problem has optimal substructure + greedy choice property
To prove greedy works, show that swapping from greedy to non-greedy never improves the answer.Example: Activity Selection (select max non-overlapping intervals)Greedy: Always pick the interval that ends earliest.Proof: Suppose optimal solution picks interval A instead of greedy’s choice B (where B ends earlier). Replace A with B. Since B ends earlier, it can’t conflict with anything A didn’t conflict with. So we still have a valid solution with at least as many intervals.
Problem: Merge elements repeatedly, cost = sum of merged elements. Minimize total cost.Greedy Strategy: Always merge the two smallest.
Copy
long long minMergeCost(vector<int>& arr) { priority_queue<long long, vector<long long>, greater<long long>> pq(arr.begin(), arr.end()); long long totalCost = 0; while (pq.size() > 1) { long long a = pq.top(); pq.pop(); long long b = pq.top(); pq.pop(); totalCost += a + b; pq.push(a + b); } return totalCost;}
Problem: Circular route with gas stations. Find starting point to complete circuit.Greedy Insight: If total gas ≥ total cost, solution exists. Start from the point where running sum is minimum.
Copy
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { int total = 0, tank = 0, start = 0; for (int i = 0; i < gas.size(); i++) { int diff = gas[i] - cost[i]; total += diff; tank += diff; if (tank < 0) { start = i + 1; // Can't start from any point before i+1 tank = 0; } } return total >= 0 ? start : -1;}
Mistake 1: Assuming Greedy Without Proof
Always verify with edge cases or construct a counter-example:
Copy
Greedy for 0/1 Knapsack?Items: [(5, 10), (4, 40), (6, 30)] // (weight, value)Capacity: 10Greedy by ratio: take (4, 40) then can't fit restValue = 40Optimal: take (4, 40) and (6, 30) doesn't fit... Wait, take (5, 10) and... noActually take (4, 40): W=6 left, take nothing else = 40Or take (5, 10) and nothing else = 10Or take (6, 30) and (4, 40) = doesn't fitActually optimal is 40 + 30 if we could fit (6)... can't.Greedy works here, but in general 0/1 knapsack needs DP.
Mistake 2: Wrong Sorting Criteria
Small changes in sort order can break greedy:
Copy
// WRONG for interval schedulingsort(intervals.begin(), intervals.end()); // Sorts by start time// CORRECTsort(intervals.begin(), intervals.end(), [](auto& a, auto& b) { return a.second < b.second; // Sort by END time});
Mistake 3: Greedy When DP Needed
Signals that greedy fails:
Can you sort the input?├── Yes → After sorting, can you process greedily?│ ├── Yes → Try to prove with exchange argument│ │ ├── Proof works → Use greedy│ │ └── Can't prove → Test edge cases, maybe DP│ └── No clear greedy order → Likely DP or other└── No → Is there a single pass greedy? ├── Yes → Track running max/min/sum └── No → Probably not greedy