Greedy

Medium
Time: O(n) or O(n log n) with sortingSpace: O(1) to O(n) depending on problem
0/3
problems solved
0%
1.

What is a Greedy Algorithm?

A greedy algorithm builds a solution piece by piece, always choosing the option that looks best at the moment. Unlike dynamic programming, it never reconsiders its choices - once a decision is made, it's final.

Think of it like eating at a buffet: at each station, you pick whatever looks most appealing right now, without planning your entire meal in advance. Sometimes this strategy works perfectly (you end up with a great meal), and sometimes it backfires (you fill up on appetizers and miss the steak).

The key question with any greedy approach is: does making the locally optimal choice at each step lead to a globally optimal solution? When the answer is yes, greedy algorithms are powerful because they're simple and fast.

2.

When Does Greedy Work?

Greedy algorithms work when a problem has two special properties:

  1. Greedy-Choice Property: You can build an optimal solution by making locally optimal choices. The best choice right now won't prevent you from finding the best overall solution.

  2. Optimal Substructure: After making a greedy choice, the remaining problem is a smaller version of the original. The optimal solution to the whole problem contains optimal solutions to subproblems.

A classic example where greedy works is making change with US coins (25, 10, 5, 1 cents). To make 67 cents with the fewest coins, you always pick the largest coin that fits: 25 + 25 + 10 + 5 + 1 + 1 = 6 coins. This greedy approach happens to give the optimal answer.

But change the coins to [1, 3, 4] and try to make 6 cents. Greedy picks 4 + 1 + 1 = 3 coins, but optimal is 3 + 3 = 2 coins. Here, greedy fails because the problem lacks the greedy-choice property.

3.

The Classic Example: Activity Selection

The activity selection problem is the textbook example of greedy algorithms. You have a list of activities, each with a start and end time, and you want to attend as many non-overlapping activities as possible.

The greedy insight is: always pick the activity that ends earliest. Why? Because finishing early leaves the maximum time for remaining activities. Any other choice would occupy time that could fit more activities.

Here's how it works: sort all activities by their end time, then iterate through them. If an activity starts after the previous one ended, take it.

Code
Java

Interactive Activity Selection

Activity Selection Visualizer

Greedy: Sort by end time, always pick earliest ending activity

Speed:
1Original
2Sort
3Ready
4Select
0123456789101112
A (1-4)
B (3-5)
C (0-6)
D (5-7)
E (3-9)
F (5-9)
G (6-10)
H (8-11)
Activities shown in original order. Click Play to start!
8
Total Activities
0
Selected
0/8
Processed
Current
Selected
Skipped
4.

Jump Game: Tracking Maximum Reach

The Jump Game problem asks: given an array where each element tells you the maximum jump length from that position, can you reach the last index?

The greedy approach is elegant: as you walk through the array, track the furthest position you could possibly reach. If at any point your current position exceeds this maximum reach, you're stuck and can't proceed.

This works because we don't care about the exact path - we only care whether reaching the end is possible. By always updating our maximum reach, we're implicitly considering all possible jump sequences.

Code
Java

Interactive Jump Game

Jump Game Visualizer

Greedy: Track the maximum reachable position

Speed:
nums = [2, 3, 1, 1, 4]
i=0
2
Start
i=1
3
i=2
1
i=3
1
i=4
4
Goal
Current Index
0
Max Reach
0
Target
4
maxReach = max(0, 0 + 2) = max(0, 2) = 2

Greedy Insight:At each position, update the maximum reachable index. If current index exceeds maxReach, we're stuck. If maxReach >= last index, we can reach the end.

5.

Jump Game II: Minimum Jumps with BFS Thinking

Now the question changes: what's the minimum number of jumps to reach the end? This requires a slightly different greedy strategy.

Think of it like BFS levels. Each "level" contains all positions reachable with the same number of jumps. We track the boundary of the current level (currentEnd) and the farthest position reachable in the next level (farthest).

When we reach the current level's boundary, we must jump. The new boundary becomes wherever we could reach, and we increment our jump count. This greedy approach works because we're always extending our reach as far as possible before committing to a jump.

Code
Java

Interactive Jump Game

Jump Game Visualizer

Greedy: Track the maximum reachable position

Speed:
nums = [2, 3, 1, 1, 4]
i=0
2
Start
i=1
3
i=2
1
i=3
1
i=4
4
Goal
Current Index
0
Max Reach
0
Target
4
maxReach = max(0, 0 + 2) = max(0, 2) = 2

Greedy Insight:At each position, update the maximum reachable index. If current index exceeds maxReach, we're stuck. If maxReach >= last index, we can reach the end.

6.

Greedy vs Dynamic Programming

The key difference: greedy makes one choice and moves on, while DP explores all choices and picks the best.

Use greedy when:

  • There's a clear "best" choice at each step that doesn't hurt future options
  • The problem has the greedy-choice property (you can prove local optimal leads to global optimal)
  • Sorting creates an obvious order to process items

Use DP when:

  • Choices have cascading effects on future options
  • You need to try combinations to find the best
  • The problem asks for "number of ways" or optimal value where greedy would miss some options

A good test: try a few examples with your greedy approach. If you can find a counterexample where greedy gives the wrong answer, you likely need DP.

Example: Fractional Knapsack (can take fractions of items) -> Greedy works (sort by value/weight ratio) 0/1 Knapsack (must take whole items) -> Need DP (taking a heavy valuable item might block better combinations)

7.

Common Greedy Patterns to Recognize

  1. Interval Scheduling: Sort by end time to maximize non-overlapping intervals. Problems like activity selection, minimum meeting rooms, and non-overlapping intervals all use this pattern.

  2. Merge Intervals: Sort by start time when you need to combine overlapping intervals. Different from scheduling because you're merging, not selecting.

  3. Two-Pointer Greedy: Use two pointers from both ends, greedily moving the "worse" pointer. Container With Most Water and Boats to Save People use this.

  4. Running Maximum/Minimum: Track the best value seen so far as you iterate. Jump Game tracks maximum reach; Best Time to Buy and Sell Stock tracks minimum price seen.

  5. Task Scheduling: Often involves heaps to always pick the "best" available task. Task Scheduler and Reorganize String use this pattern.

8.

Best Time to Buy and Sell Stock

The classic running minimum problem. Find maximum profit from one buy-sell transaction.

The Greedy Insight: To maximize profit, we want to buy at the LOWEST price so far. As we scan through prices, we:

  1. Track the minimum price seen
  2. At each day, calculate profit if we sell today
  3. Update maximum profit if current profit is better

Visual Trace: [7, 1, 5, 3, 6, 4]

Day 0: price=7
minPrice = 7
profit = 7 - 7 = 0
maxProfit = 0
Day 1: price=1
minPrice = 1 (new minimum!)
profit = 1 - 1 = 0
maxProfit = 0
Day 2: price=5
minPrice = 1
profit = 5 - 1 = 4
maxProfit = 4 ★
Day 3: price=3
minPrice = 1
profit = 3 - 1 = 2
maxProfit = 4
Day 4: price=6
minPrice = 1
profit = 6 - 1 = 5 ★
maxProfit = 5
Day 5: price=4
minPrice = 1
profit = 4 - 1 = 3
maxProfit = 5
Answer: 5 (buy at 1, sell at 6)

Why Greedy Works: We're guaranteed to find the optimal because:

  • We always know the best buy price seen so far
  • At each potential sell day, we compute the best possible profit
Code
Java
9.

Gas Station: Circular Greedy

Can you complete a circular route starting from some gas station? If yes, which one?

Problem Setup:

  • N gas stations in a circle
  • gas[i] = gas available at station i
  • cost[i] = gas needed to travel from station i to i+1

Key Insight 1: If total gas >= total cost, a solution EXISTS.

Key Insight 2: If we fail at station j starting from i, then stations i+1 to j also cannot be starting points. So we try starting from j+1.

Visual Trace:

gas = [1, 2, 3, 4, 5]
cost = [3, 4, 5, 1, 2]
Start at 0:
tank = 1 - 3 = -2 FAIL at 0
Try starting at 1
Start at 1:
tank = 2 - 4 = -2 FAIL at 1
Try starting at 2
Start at 2:
tank = 3 - 5 = -2 FAIL at 2
Try starting at 3
Start at 3:
Station 3: tank = 4 - 1 = 3
Station 4: tank = 3 + 5 - 2 = 6
Station 0: tank = 6 + 1 - 3 = 4
Station 1: tank = 4 + 2 - 4 = 2
Station 2: tank = 2 + 3 - 5 = 0 ✓
Answer: Start at station 3
Code
Java
10.

Boats to Save People: Two-Pointer Greedy

Each boat holds at most 2 people with weight limit. Minimize boats needed.

The Greedy Strategy: Pair the lightest person with the heaviest person if possible. This maximizes boat utilization.

Algorithm:

  1. Sort people by weight
  2. Two pointers: lightest (left) and heaviest (right)
  3. If they fit together, move both pointers
  4. If not, only the heaviest goes (they need a boat alone)

Visual Trace: people = [3, 2, 2, 1], limit = 3

Sorted: [1, 2, 2, 3]
L R
Boat 1: 1 + 3 = 4 > 3
Only 3 goes
Boats = 1, [1, 2, 2, _]
L R
Boat 2: 1 + 2 = 3 <= 3
Both go!
Boats = 2, [_, _, 2, _]
LR
Boat 3: Only 2 left
Boats = 3
Answer: 3 boats

Why This Works:

  • Heavy people might need solo boats
  • Light people should pair with someone
  • Pairing lightest with heaviest maximizes the chance the heaviest can share
Code
Java
11.

Partition Labels: Greedy Interval Building

Partition a string into max parts so each letter appears in at most one part.

The Greedy Insight: For each character, find its LAST occurrence. A partition must extend at least until the last occurrence of every character it contains.

Algorithm:

  1. Pre-compute last index of each character
  2. Scan string, tracking the farthest we must extend
  3. When current index equals farthest, we can end a partition

Visual Trace: "ababcbacadefegdehijhklij"

Last occurrences:
a:8, b:5, c:7, d:14, e:15, f:11, g:13, h:19, i:22, j:23, k:20, l:21
Scanning:
i=0 'a': must go to 8, end=8
i=1 'b': must go to 5, end=8
i=2 'a': must go to 8, end=8
...
i=8 'a': i == end! Partition [0..8] = "ababcbaca" (9 chars)
i=9 'd': must go to 14, end=14
i=10 'e': must go to 15, end=15
...
i=15 'e': i == end! Partition [9..15] = "defegde" (7 chars)
Continue for "hijhklij" (8 chars)
Result: [9, 7, 8]
Code
Java

Interactive Activity Selection

Activity Selection Visualizer

Greedy: Sort by end time, always pick earliest ending activity

Speed:
1Original
2Sort
3Ready
4Select
0123456789101112
A (1-4)
B (3-5)
C (0-6)
D (5-7)
E (3-9)
F (5-9)
G (6-10)
H (8-11)
Activities shown in original order. Click Play to start!
8
Total Activities
0
Selected
0/8
Processed
Current
Selected
Skipped
12.

Queue Reconstruction by Height

Reconstruct a queue from (height, k) pairs where k = number of taller people in front.

The Greedy Insight: Process people from tallest to shortest. When inserting a person, their k value tells us exactly where to insert (since all already-placed people are taller or equal).

Algorithm:

  1. Sort by height DESC, then by k ASC
  2. Insert each person at index = k

Visual Trace: [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]

Sorted: [[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]
Insert [7,0] at index 0: [[7,0]]
Insert [7,1] at index 1: [[7,0],[7,1]]
Insert [6,1] at index 1: [[7,0],[6,1],[7,1]]
Insert [5,0] at index 0: [[5,0],[7,0],[6,1],[7,1]]
Insert [5,2] at index 2: [[5,0],[7,0],[5,2],[6,1],[7,1]]
Insert [4,4] at index 4: [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
Result: [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

Why This Works: Taller people don't "see" shorter people. So when we insert a shorter person, their k value stays valid regardless of shorter people inserted later.

Code
Java
13.

Non-Overlapping Intervals: Minimizing Removals

Find the minimum number of intervals to remove to make the rest non-overlapping.

Key Insight: This is equivalent to finding the MAXIMUM number of non-overlapping intervals (activity selection), then subtracting from total.

Algorithm:

  1. Sort by END time
  2. Greedily select intervals that don't overlap
  3. Answer = total - selected

Visual Trace: [[1,2],[2,3],[3,4],[1,3]]

Sorted by end: [[1,2],[2,3],[1,3],[3,4]]
[1,2]: Select! end = 2
[2,3]: start(2) >= end(2), Select! end = 3
[1,3]: start(1) < end(3), Overlap! Skip
[3,4]: start(3) >= end(3), Select! end = 4
Selected: 3 intervals
Remove: 4 - 3 = 1 interval
Code
Java

Interactive Activity Selection

Activity Selection Visualizer

Greedy: Sort by end time, always pick earliest ending activity

Speed:
1Original
2Sort
3Ready
4Select
0123456789101112
A (1-4)
B (3-5)
C (0-6)
D (5-7)
E (3-9)
F (5-9)
G (6-10)
H (8-11)
Activities shown in original order. Click Play to start!
8
Total Activities
0
Selected
0/8
Processed
Current
Selected
Skipped
14.

Proving Greedy Correctness

In interviews, you may need to explain WHY greedy works. Two common proof techniques:

1. Greedy Stays Ahead Show that at every step, greedy's solution is at least as good as any other approach.

Example - Activity Selection:

Proposition: Greedy selects ≥ as many activities as optimal
Proof by induction:
- Base: First greedy choice (earliest end) doesn't block
more activities than any other first choice
- Inductive: If greedy has k activities, replacing any
with a later-ending one can only block more, not fewer

2. Exchange Argument Show that any optimal solution can be transformed into greedy's solution without losing optimality.

Example - Fractional Knapsack:

Proposition: Sort by value/weight ratio, take greedily
Proof: If optimal solution differs from greedy:
- Find first item where they differ
- Swap some of optimal's choice with greedy's choice
- New solution has ≥ value (by ratio ordering)
- So greedy's choice is at least as good

When to Suspect Greedy Fails:

  • Choices affect future options in complex ways
  • Problem asks for count of ways (usually DP)
  • Small examples show counterexamples
  • Problem has "0/1" constraint (take all or nothing)
15.

Pattern Recognition Guide

Quick reference for identifying greedy problems:

Problem SignalLikely Greedy Approach
"Maximum number of non-overlapping"Sort by END time
"Minimum number to remove"Find max non-overlapping, subtract
"Can you reach the end"Track maximum reach
"Minimum jumps to reach"BFS-like level tracking
"Partition into parts"Track farthest required extent
"Pair items optimally"Sort, two-pointer from ends
"Buy low, sell high"Track running minimum
"Circular problem"Check total, find breaking point
"Reconstruct from constraints"Process in specific order
"Assign to minimize max"Binary search on answer + greedy check

Decision Tree:

Can I sort the input?
├── YES → Does sorting reveal optimal order?
│ ├── YES → Greedy likely works
│ └── NO → Consider DP
└── NO → Is there a running max/min pattern?
├── YES → Track and update greedily
└── NO → Probably not greedy
Does my greedy choice ever hurt future options?
├── YES → Use DP instead
└── NO → Greedy is valid
16.

Common Mistakes and Tips

Mistake 1: Wrong Sort Key

// WRONG: For activity selection, sorting by START time
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// CORRECT: Sort by END time
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);

Mistake 2: Not Proving Greedy Works

// DANGEROUS: Assuming greedy works without verification
// Always test with examples and consider counterexamples!
Counterexample for coin change [1, 3, 4], target 6:
- Greedy: 4 + 1 + 1 = 3 coins
- Optimal: 3 + 3 = 2 coins
- Greedy FAILS → Use DP

Mistake 3: Off-by-One in Interval Comparisons

// Touching intervals [1,2] and [2,3]:
// Are they overlapping or not?
// If touching is NOT overlap:
if (intervals[i][0] >= lastEnd) // >= allows touching
// If touching IS overlap:
if (intervals[i][0] > lastEnd) // > means gap required

Mistake 4: Forgetting to Track State

// WRONG: Not updating tracking variable
for (let i = 0; i < n; i++) {
if (nums[i] > maxReach) return false;
// FORGOT: maxReach = Math.max(maxReach, i + nums[i]);
}
// CORRECT: Update state each iteration
maxReach = Math.max(maxReach, i + nums[i]);

Pro Tips:

  1. Always verify greedy with 3-4 examples before coding
  2. Think about why greedy choice doesn't hurt future
  3. Many greedy problems combine with sorting
  4. If stuck, consider if problem has optimal substructure for DP
17.

Complexity Analysis

Time Complexity Patterns:

Problem TypeComplexityBottleneck
Activity SelectionO(n log n)Sorting
Jump GameO(n)Single scan
Buy/Sell StockO(n)Single scan
Gas StationO(n)Single scan
Partition LabelsO(n)Two passes
Queue ReconstructionO(n² or n log n)Insertions
Boats to Save PeopleO(n log n)Sorting

Space Complexity:

  • Most greedy: O(1) extra space (or O(n) for result)
  • Sorting in-place: O(1) or O(log n) for recursion
  • Pre-computing (like last indices): O(alphabet size)

Why Greedy is Often Fast:

  • Single or few passes through data
  • No recursion with branching
  • No memoization table needed
  • Sorting is often the slowest step

Comparison with DP:

Problem Greedy DP
─────────────────────────────────────────────
Activity Selection O(n log n) O(n log n) or O(n²)
0/1 Knapsack N/A (fails) O(n × W)
Coin Change Sometimes O(n) O(n × amount)
Longest Increasing N/A (fails) O(n log n) or O(n²)

When greedy works, it's almost always faster and simpler than DP!

18.

Template Summary

Template 1: Running Maximum/Minimum

int best = initialValue;
for (int i = 0; i < n; i++) {
// Update tracking variable
best = Math.max/min(best, arr[i]);
// Use best to make decision or update result
}

Template 2: Interval Selection (Max Non-Overlap)

Arrays.sort(intervals, (a, b) -> a[1] - b[1]); // Sort by END
int count = 1, lastEnd = intervals[0][1];
for (int i = 1; i < n; i++) {
if (intervals[i][0] >= lastEnd) { // No overlap
count++;
lastEnd = intervals[i][1];
}
}

Template 3: Two-Pointer Greedy

Arrays.sort(arr);
int left = 0, right = n - 1;
while (left <= right) {
if (canPair(arr[left], arr[right])) {
// Process pair
left++;
right--;
} else {
// Only process one
right--; // or left++
}
}

Template 4: Greedy with Pre-computation

// Pre-compute helper data
int[] helper = precompute(input);
// Single pass using pre-computed data
for (int i = 0; i < n; i++) {
// Make greedy choice using helper[i]
}

Template 5: Circular Array Greedy

int total = 0, current = 0, start = 0;
for (int i = 0; i < n; i++) {
total += gain[i] - cost[i];
current += gain[i] - cost[i];
if (current < 0) {
start = i + 1;
current = 0;
}
}
return total >= 0 ? start : -1;

Test Your Knowledge

Take a quick 15question quiz to test what you've learned.