Skip to content
beginner Phase 1 · Foundation

Greedy Algorithms

Learn greedy choice property, interval scheduling, and when greedy works vs DP.

1h 30m
5 problems
Topic Progress 0%

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:

  1. Greedy Choice Property: A locally optimal choice leads to a globally optimal solution
  2. 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

  1. Sorting: Sort by some criteria, process in order
  2. Priority Queue: Always pick the best element
  3. Interval Scheduling: Sort by end time, pick non-overlapping
  4. Huffman-like: Combine smallest two repeatedly

Greedy Proof (Exchange Argument)

To prove greedy is optimal:

  1. Assume there's a better solution B
  2. Find the first place B differs from greedy G
  3. Show swapping makes B no worse than G
  4. 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

  1. Assume optimal solution O differs from greedy G
  2. Find first position where they differ
  3. Show swapping makes O no better than G
  4. Contradiction: O was optimal

Method 2: Greedy Stays Ahead

  1. Define a metric that greedy maximizes
  2. Show greedy is always ahead at each step
  3. Therefore greedy is optimal

Method 3: Matroid Theory

  • If the problem forms a matroid, greedy is optimal
  • Examples: MST, activity selection

Common Greedy Pitfalls

  1. Assuming greedy always works: Must prove it!
  2. Wrong sorting criterion: Different sorts give different results
  3. Missing edge cases: Empty input, single element
  4. 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

0 / 6 solved
Best Time to Buy and Sell Stock
Greedy - Track Minimum

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

Edge Cases:

  • Single element: return 0
  • All decreasing: return 0
  • All increasing: return last - first
  • All same prices: return 0
Jump Game
Greedy - Reachability

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.

Edge Cases:

  • Single element [0]: already at end, return true
  • First element is 0 but array has one element: return true
  • All zeros except first: need first to reach end
Assign Cookies
Greedy - Two Pointers

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

Edge Cases:

  • No cookies: return 0
  • No children: return 0
  • All cookies too small: return 0
  • All cookies large enough: return min(children, cookies)
Merge Intervals
Greedy - Interval Merge

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

Example:

Input: intervals = [[1,3],[2,6],[8,10],[15,18]]

Output: [[1,6],[8,10],[15,18]]

Since intervals [1,3] and [2,6] overlap, merge them into [1,6].

Edge Cases:

  • Single interval (return it as-is)
  • All intervals overlap into one (return single merged interval)
  • No intervals overlap (return all intervals unchanged)
  • Intervals that touch at endpoints (e.g., [1,4] and [4,5] — merge into [1,5])
Insert Interval
Interval Insertion

You are given an array of non-overlapping intervals sorted by their start times. Insert a new interval into the intervals (if necessary) and merge all necessary intervals.

Example:

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]

Output: [[1,5],[6,9]]

Insert [2,5] and merge with [1,3] to get [1,5].

Edge Cases:

  • Empty intervals array — return [newInterval]
  • New interval doesn't overlap with any — insert in correct position
  • New interval overlaps with all intervals — merge into single interval
  • New interval is completely inside an existing interval
  • New interval completely contains all existing intervals
Non-overlapping Intervals
Greedy - Activity Selection

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

Quiz

1. What are the two properties that make a problem solvable by greedy?

Question 1 options

2. In interval scheduling, why do we sort by end time instead of start time?

Question 2 options

3. When does greedy fail for coin change?

Question 3 options

Flashcards

Question

What is the greedy choice property?

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?

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?

Answer

Exchange argument: assume optimal differs from greedy, find first difference, show swapping makes optimal no better, contradiction.

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:

  1. Assume optimal O differs from greedy G
  2. Find first position where they differ
  3. Show swapping makes O no better than G
  4. 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:

  1. Assuming greedy works without proof
  2. Wrong sorting criterion
  3. Missing edge cases