What is Greedy
A greedy algorithm makes the locally optimal choice at each step, hoping to find a global optimum.
Real-World Analogy
You're at a vending machine with coins. You need to give someone 41 cents. What do you do?
- Greedy: Use the largest coin possible each time
- 25 cents (remaining: 16)
- 10 cents (remaining: 6)
- 5 cents (remaining: 1)
- 1 cent (remaining: 0)
- Total: 4 coins
This works because US coins have the right properties. But it doesn't always work — see the counterexample below.
When Greedy Works
Greedy works when the problem has two properties:
- Greedy Choice Property: A locally optimal choice leads to a globally optimal solution
- Optimal Substructure: An optimal solution contains optimal solutions to subproblems
When Greedy Fails
Counterexample: Coin Change
Coins: [1, 3, 4], Target: 6
- Greedy: 4 + 1 + 1 = 6 (3 coins)
- Optimal: 3 + 3 = 6 (2 coins)
Greedy fails because the coin system doesn't have the greedy choice property.
Greedy vs DP
| Aspect | Greedy | DP |
|---|---|---|
| Approach | Local optimal | All subproblems |
| Speed | Usually O(n log n) | Usually O(n²) or O(n) |
| Correctness | Must prove | Always correct |
| When to use | Greedy choice property holds | Optimal substructure + overlapping subproblems |
How to Identify Greedy Problems
Signals:
- "Maximum" or "Minimum" in the question
- "Fewest" or "Most" number of items
- "Can you..." (yes/no with optimal strategy)
- Interval scheduling
- Assignment problems
Keywords:
- "At each step"
- "Choose the best"
- "Optimal"
Common Greedy Patterns
- Sorting: Sort by some criteria, process in order
- Priority Queue: Always pick the best element
- Interval Scheduling: Sort by end time, pick non-overlapping
- Huffman-like: Combine smallest two repeatedly
Greedy Proof (Exchange Argument)
To prove greedy is optimal:
- Assume there's a better solution B
- Find the first place B differs from greedy G
- Show swapping makes B no worse than G
- This contradicts B being better
This proves greedy is at least as good as any other solution.
Greedy Patterns and Templates
Pattern 1: Sort and Scan
Sort by some criteria, then make greedy choices.
// Activity Selection: maximum non-overlapping activities
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] - b[1]); // Sort by end time
int count = 0;
int lastEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= lastEnd) {
count++; // No overlap, keep it
lastEnd = intervals[i][1];
}
}
return intervals.length - count; // Remove minimum
}
Pattern 2: Priority Queue (Heap)
Always pick the best element.
// Task Scheduler: minimum intervals with cooldown
public int leastInterval(char[] tasks, int n) {
int[] count = new int[26];
for (char c : tasks) count[c - 'A']++;
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
for (int c : count) {
if (c > 0) pq.offer(c);
}
int intervals = 0;
while (!pq.isEmpty()) {
List<Integer> temp = new ArrayList<>();
for (int i = 0; i <= n; i++) {
if (!pq.isEmpty()) {
temp.add(pq.poll() - 1);
}
}
for (int t : temp) {
if (t > 0) pq.offer(t);
}
intervals += pq.isEmpty() ? temp.size() : n + 1;
}
return intervals;
}
Pattern 3: Interval Scheduling
Sort by end time, pick greedily.
// Maximum meetings in one room
public int maxMeetings(int[] start, int[] end) {
int n = start.length;
int[][] meetings = new int[n][2];
for (int i = 0; i < n; i++) {
meetings[i][0] = start[i];
meetings[i][1] = end[i];
}
Arrays.sort(meetings, (a, b) -> a[1] - b[1]);
int count = 1;
int lastEnd = meetings[0][1];
for (int i = 1; i < n; i++) {
if (meetings[i][0] > lastEnd) {
count++;
lastEnd = meetings[i][1];
}
}
return count;
}
Pattern 4: Jump Game
Track the farthest reachable position.
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
}
Pattern Selection Guide
| Pattern | Signal | Example |
|---|---|---|
| Sort and Scan | "Maximum" or "Minimum" count | Activity Selection, Merge Intervals |
| Priority Queue | "Always pick best" | Task Scheduler, Huffman |
| Interval Scheduling | Non-overlapping intervals | Meeting Rooms, Non-overlapping Intervals |
| Jump Game | Reachability | Jump Game, Jump Game II |
Greedy with Intervals
Interval problems are a classic greedy category.
Interval Sorting
Always sort intervals first. The sorting criterion determines the greedy strategy.
| Sort By | Strategy | Example |
|---|---|---|
| Start time | Process in order | Merge Intervals |
| End time | Pick non-overlapping | Activity Selection |
| Length | Pick shortest | Minimum Removal |
Merge Intervals
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> merged = new ArrayList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
merged.add(interval);
} else {
merged.get(merged.size() - 1)[1] = Math.max(
merged.get(merged.size() - 1)[1], interval[1]);
}
}
return merged.toArray(new int[0][]);
}
Non-overlapping Intervals
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 0;
int lastEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= lastEnd) {
lastEnd = intervals[i][1];
} else {
count++; // Must remove this interval
}
}
return count;
}
Insert Interval
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> result = new ArrayList<>();
int i = 0;
// Add all intervals before newInterval
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
result.add(intervals[i++]);
}
// Merge overlapping intervals
while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.add(newInterval);
// Add remaining intervals
while (i < intervals.length) {
result.add(intervals[i++]);
}
return result.toArray(new int[0][]);
}
Meeting Rooms II (Minimum rooms needed)
public int minMeetingRooms(int[][] intervals) {
if (intervals.length == 0) return 0;
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(intervals[0][1]);
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= pq.peek()) {
pq.poll(); // Reuse room
}
pq.offer(intervals[i][1]);
}
return pq.size();
}
When Interval Greedy Works
- Merge Intervals: Sort by start, merge overlapping
- Non-overlapping: Sort by end, pick greedily
- Minimum removals: Convert to non-overlapping problem
- Maximum meetings: Sort by end, pick greedily
Greedy Complexity and Correctness
Time Complexity
| Pattern | Complexity | Why |
|---|---|---|
| Sort and Scan | O(n log n) | Sorting dominates |
| Priority Queue | O(n log n) | Heap operations |
| Interval Scheduling | O(n log n) | Sorting |
| Two Pointers | O(n) | Single pass |
Proving Greedy Correctness
Method 1: Exchange Argument
- Assume optimal solution O differs from greedy G
- Find first position where they differ
- Show swapping makes O no better than G
- Contradiction: O was optimal
Method 2: Greedy Stays Ahead
- Define a metric that greedy maximizes
- Show greedy is always ahead at each step
- Therefore greedy is optimal
Method 3: Matroid Theory
- If the problem forms a matroid, greedy is optimal
- Examples: MST, activity selection
Common Greedy Pitfalls
- Assuming greedy always works: Must prove it!
- Wrong sorting criterion: Different sorts give different results
- Missing edge cases: Empty input, single element
- Overflow: When computing sums or products
Greedy vs DP Decision Tree
Is there optimal substructure?
├── No → Not solvable optimally
└── Yes
├── Are overlapping subproblems present?
│ ├── Yes → Use Dynamic Programming
│ └── No
│ ├── Can you prove greedy choice property?
│ │ ├── Yes → Use Greedy
│ │ └── No → Use DP or Backtracking
│ └── Is speed critical?
│ ├── Yes → Try Greedy first
│ └── No → Either works
Greedy Problem Checklist
- Can you define a local choice?
- Does the choice lead to global optimum?
- Can you prove correctness?
- What's the sorting criterion?
- Edge cases handled?
Practice Problems
You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy and a single day to sell in the future.
Example:
Input: prices = [7,1,5,3,6,4]
Output: 5
Buy on day 2 (price=1), sell on day 5 (price=6), profit=5
Optimal Solution — O(n) time, O(1) space
Greedy: track minimum price seen so far, maximize profit at each step
class Solution {
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
maxProfit = Math.max(maxProfit, price - minPrice);
}
return maxProfit;
}
}Edge Cases:
- Prices always decreasing: return 0
- Single price: return 0
- All same prices: return 0
You are given an integer array nums. You are initially positioned at the array's first index. Each element in the array represents your maximum jump length. Return true if you can reach the last index.
Example:
Input: nums = [2,3,1,1,4]
Output: true
Jump 1 step from index 0 to 1, then 3 steps to the last index
Optimal Solution — O(n) time, O(1) space
Greedy: track farthest reachable position at each step
class Solution {
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
}
}Edge Cases:
- Single element: true
- First element is 0: false (unless only element)
- All zeros except first: check reachability
Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals.
Example:
Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Intervals [1,3] and [2,6] overlap, merge to [1,6]
Optimal Solution — O(n log n) time, O(n) for result space
Sort by start time, merge overlapping intervals
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> merged = new ArrayList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
merged.add(interval);
} else {
merged.get(merged.size() - 1)[1] = Math.max(
merged.get(merged.size() - 1)[1], interval[1]);
}
}
return merged.toArray(new int[0][]);
}
}Edge Cases:
- No overlapping intervals: return all
- All overlapping: return single interval
- Single interval: return it
Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.
Example:
Input: intervals = [[1,2],[2,3],[3,4],[1,3]]
Output: 1
Remove [1,3] to make the rest non-overlapping
Optimal Solution — O(n log n) time, O(1) space
Sort by end time, greedily pick non-overlapping intervals
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 0;
int lastEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= lastEnd) {
lastEnd = intervals[i][1];
} else {
count++;
}
}
return count;
}
}Edge Cases:
- No overlapping: return 0
- All overlapping: return n-1
- Single interval: return 0
Assume you are an awesome parent and want to give your children some cookies. Each child i has a greed factor g[i], and each cookie j has a size s[j]. A child will be content if the cookie's size is >= the child's greed factor. Maximize the number of content children.
Example:
Input: g = [1,2,3], s = [1,1]
Output: 1
Child with greed 1 gets cookie size 1
Optimal Solution — O(n log n + m log m) time, O(1) space
Sort both, use two pointers to match smallest cookie to smallest greed
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int child = 0, cookie = 0;
while (child < g.length && cookie < s.length) {
if (s[cookie] >= g[child]) {
child++;
}
cookie++;
}
return child;
}
}Edge Cases:
- No cookies: return 0
- No children: return 0
- All cookies too small: return 0
Quiz
1. What are the two properties that make a problem solvable by greedy?
2. In interval scheduling, why do we sort by end time instead of start time?
3. When does greedy fail for coin change?
4. What is the primary purpose of Greedy Algorithms?
Flashcards
Question
What is the greedy choice property?
Click to reveal answer
Answer
A locally optimal choice at each step leads to a globally optimal solution. This must be proven for greedy to work.
Question
When should you sort intervals by end time vs start time?
Click to reveal answer
Answer
End time for activity selection (maximize count). Start time for merging intervals (group overlapping).
Question
How do you prove a greedy algorithm is correct?
Click to reveal answer
Answer
Exchange argument: assume optimal differs from greedy, find first difference, show swapping makes optimal no better, contradiction.
Question
What is Greedy Algorithms?
Click to reveal answer
Answer
Greedy Algorithms is a key concept in software engineering.
Question
When to use Greedy Algorithms?
Click to reveal answer
Answer
Use Greedy Algorithms when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Greedy makes locally optimal choices hoping for global optimum
- 2.Must prove greedy choice property and optimal substructure
- 3.Sort by end time for activity selection, start time for merging
- 4.Greedy fails when the problem requires considering all subproblems (use DP)
- 5.Exchange argument is the standard proof technique
Interview Tips
- •Try greedy first if the problem asks for maximum/minimum
- •If greedy doesn't work, consider DP
- •Always mention the proof sketch in interviews
- •Sort intervals by end time for non-overlapping problems
- •Edge cases: empty input, single element, all overlapping
Cheat Sheet
Greedy Algorithms Cheat Sheet
When to Use:
- "Maximum" or "Minimum" in question
- Local choice leads to global optimum
- No overlapping subproblems
Patterns:
| Pattern | Sort By | Example |
|---|---|---|
| Activity Selection | End time | Non-overlapping Intervals |
| Interval Merge | Start time | Merge Intervals |
| Jump Game | None | Track max reach |
| Two Pointers | Both arrays | Assign Cookies |
Proof Technique:
- Assume optimal O differs from greedy G
- Find first position where they differ
- Show swapping makes O no better than G
- Contradiction: greedy is optimal
Greedy vs DP:
- Greedy: O(n log n) usually, must prove correctness
- DP: O(n²) or O(n), always correct
- Use greedy when speed matters AND you can prove it works
Common Mistakes:
- Assuming greedy works without proof
- Wrong sorting criterion
- Missing edge cases