LeetCopilot Logo
LeetCopilot
LeetCode Pattern/Greedy Algorithm/Greedy vs Dynamic Programming: When to Use Each, How to Decide, and Common Traps

Greedy vs Dynamic Programming: When to Use Each, How to Decide, and Common Traps

LeetCopilot Team
Dec 30, 2025
18 min read
Greedy AlgorithmDynamic ProgrammingAlgorithm ComparisonInterview PrepPattern Recognition
Master the critical distinction between greedy and DP. Learn systematic decision frameworks, understand when greedy fails, see counterexamples, and avoid the #1 algorithm interview mistake.

You're in an interview. The problem says: "Find the minimum number of coins to make change for amount n."

You've seen this pattern before. You write a greedy solution:

python
def coinChange(coins, amount):
    coins.sort(reverse=True)
    count = 0
    for coin in coins:
        count += amount // coin
        amount %= coin
    return count if amount == 0 else -1

You submit. Wrong Answer.

The input was coins = [1, 3, 4], amount = 6. Your greedy solution returned 3 coins [4, 1, 1]. The optimal answer is 2 coins [3, 3].

What went wrong? You used greedy when you needed dynamic programming. This is the #1 algorithm interview mistake, and it costs more interviews than any other pattern confusion.

In this guide, you'll learn when greedy works vs when DP is required, systematic decision frameworks, how to test with counterexamples, and how to avoid this trap forever.

TL;DR

Use Greedy when:

  • ✅ Greedy choice property holds (local optimal → global optimal)
  • ✅ Can prove correctness (exchange argument, induction)
  • ✅ No counterexamples exist
  • ✅ Examples: Interval scheduling, fractional knapsack, jump game

Use DP when:

  • ❌ Greedy choice property fails
  • ❌ Counterexamples exist
  • ❌ Need to explore multiple choices at each step
  • ❌ Examples: 0/1 knapsack, coin change (arbitrary), LIS

Decision rule: Always test greedy with counterexamples. If one exists, use DP.

The Fundamental Difference

Greedy: Local Optimal → Global Optimal

Definition: Make the best immediate choice at each step, never reconsidering.

Key insight: The locally optimal choice is also globally optimal.

Example (Interval Scheduling):

code
Intervals: [(1,4), (3,5), (0,6), (5,7)]

Greedy choice: Pick interval ending earliest
  Step 1: Pick (1,4) - ends at 4
  Step 2: Pick (5,7) - ends at 7, starts after 4
  
Result: 2 intervals (optimal) ✓

Why it works: Picking earliest-ending interval leaves maximum room 
for future intervals. This greedy choice is provably optimal.

Dynamic Programming: Explore All Subproblems

Definition: Build up solutions from smaller subproblems, considering all choices at each step.

Key insight: Optimal solution depends on optimal solutions to overlapping subproblems.

Example (Coin Change):

code
Coins: [1, 3, 4], Amount: 6

DP approach: Try all coins at each step
  dp[0] = 0
  dp[1] = 1 (use coin 1)
  dp[2] = 2 (use coin 1 twice)
  dp[3] = 1 (use coin 3)
  dp[4] = 1 (use coin 4)
  dp[5] = 2 (dp[4] + coin 1 or dp[2] + coin 3)
  dp[6] = 2 (dp[3] + coin 3) ← optimal: [3, 3]

Result: 2 coins (optimal) ✓

Why greedy fails: Greedy picks 4 first (largest), then forced to use 
[4,1,1]=3 coins. DP explores all choices and finds [3,3]=2 coins.

Side-by-Side Comparison

AspectGreedyDynamic Programming
DecisionMake best immediate choiceExplore all choices
ReconsiderationNeverAlways (via subproblems)
SubproblemsSolve one subproblemSolve all subproblems
CorrectnessMust proveGuaranteed if recurrence correct
Time ComplexityUsually O(n) or O(n log n)Usually O(n²) or O(n × m)
Space ComplexityUsually O(1)Usually O(n) or O(n × m)
When it worksGreedy choice property holdsOptimal substructure + overlapping subproblems

The Decision Framework

Use this systematic approach to decide between greedy and DP:

Step 1: Is This an Optimization Problem?

code
Question: Does the problem ask for minimum, maximum, or optimal?

YES → Continue to Step 2
NO  → Greedy/DP probably not applicable

Examples:

  • ✅ "Minimum number of coins"
  • ✅ "Maximum number of intervals"
  • ✅ "Shortest path"
  • ❌ "Find all solutions" (use backtracking)
  • ❌ "Check if exists" (might be greedy or simple iteration)

Step 2: Can You Identify a Greedy Choice?

code
Question: Is there an obvious "best" choice at each step?

YES → Continue to Step 3
NO  → Likely need DP

Examples:

  • ✅ Interval scheduling: "Pick earliest-ending interval"
  • ✅ Fractional knapsack: "Pick highest value/weight ratio"
  • ❌ 0/1 Knapsack: No clear greedy choice
  • ❌ Longest increasing subsequence: No clear greedy choice

Step 3: Test with Counterexamples

code
Question: Can you find an input where greedy gives wrong answer?

YES (found counterexample) → Use DP
NO (no counterexample found) → Continue to Step 4

Critical counterexamples:

Coin Change:

python
coins = [1, 3, 4], amount = 6
Greedy: [4, 1, 1] = 3 coins
Optimal: [3, 3] = 2 coins
Greedy FAILS ✗

0/1 Knapsack:

python
weights = [10, 20, 30], values = [60, 100, 120], capacity = 50
Greedy (by value/weight): Pick item 1 (ratio=6), item 2 (ratio=5)
  Total: weight=30, value=160
Optimal: Pick item 2 and item 3
  Total: weight=50, value=220
Greedy FAILS ✗

Step 4: Can You Prove Greedy Correctness?

code
Question: Can you prove greedy choice property holds?

YES → Greedy is correct ✓
NO  → Use DP (safer)

Proof techniques:

  1. Exchange argument - swap greedy choice into optimal solution
  2. Greedy choice property - show local optimal is global optimal
  3. Induction - prove for base case and inductive step

See detailed guide: Greedy Proof Techniques

Visual Decision Tree

code
Optimization problem?
       |
       v
  Can identify greedy choice?
     /           \
   YES            NO
    |              |
    v              v
Counterexample?   Use DP
  /        \
YES        NO
 |          |
 v          v
Use DP   Can prove?
          /      \
        YES      NO
         |        |
         v        v
      Greedy   Use DP
                (safer)

When Greedy Works

Problem 1: Interval Scheduling

Problem: Maximize number of non-overlapping intervals.

Greedy choice: Pick interval ending earliest.

Why it works:

python
def maxIntervals(intervals):
    intervals.sort(key=lambda x: x[1])  # Sort by end time
    
    count = 1
    last_end = intervals[0][1]
    
    for start, end in intervals[1:]:
        if start >= last_end:
            count += 1
            last_end = end
    
    return count

# Example
intervals = [(1,3), (2,5), (4,7), (6,9)]
print(maxIntervals(intervals))  # 2: [(1,3), (4,7)]

Proof (Exchange Argument):

  1. Let OPT be an optimal solution
  2. Let g be the greedy choice (earliest end)
  3. If OPT doesn't include g, replace first interval in OPT with g
  4. New solution is still valid (g ends earlier, no new conflicts)
  5. New solution has same size as OPT
  6. Therefore, greedy choice is optimal ✓

Time: O(n log n), Space: O(1)

See detailed guide: Interval Scheduling Template

Problem 2: Fractional Knapsack

Problem: Maximize value in knapsack (can take fractions of items).

Greedy choice: Pick items by highest value/weight ratio.

Why it works:

python
def fractionalKnapsack(values, weights, capacity):
    # Calculate value/weight ratios
    items = [(v/w, w, v) for v, w in zip(values, weights)]
    items.sort(reverse=True)  # Highest ratio first
    
    total_value = 0
    
    for ratio, weight, value in items:
        if capacity >= weight:
            # Take entire item
            total_value += value
            capacity -= weight
        else:
            # Take fraction
            total_value += ratio * capacity
            break
    
    return total_value

# Example
values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
print(fractionalKnapsack(values, weights, capacity))  # 240
# Take all of item 3 (120), all of item 2 (100), 2/3 of item 1 (20)

Proof: Taking highest ratio first maximizes value per unit weight. Since we can take fractions, this is always optimal.

Time: O(n log n), Space: O(n)

Problem 3: Jump Game

Problem: Determine if you can reach the last index.

Greedy choice: Track maximum reachable position.

Why it works:

python
def canJump(nums):
    max_reach = 0
    
    for i in range(len(nums)):
        if i > max_reach:
            return False
        
        max_reach = max(max_reach, i + nums[i])
        
        if max_reach >= len(nums) - 1:
            return True
    
    return True

# Example
print(canJump([2,3,1,1,4]))  # True
print(canJump([3,2,1,0,4]))  # False

Proof: If we can't reach position i with maximum possible reach from 0..i-1, we can never reach i (or anything beyond).

Time: O(n), Space: O(1)

See detailed guide: Jump Game: Greedy vs DP

When Greedy Fails (Use DP)

Problem 1: Coin Change (Arbitrary Denominations)

Problem: Minimum coins to make change for amount n.

Why greedy fails:

python
# WRONG: Greedy approach
def coinChangeGreedy(coins, amount):
    coins.sort(reverse=True)
    count = 0
    
    for coin in coins:
        count += amount // coin
        amount %= coin
    
    return count if amount == 0 else -1

# Counterexample
coins = [1, 3, 4]
amount = 6

# Greedy: Pick 4 (largest), then 1, 1 → [4,1,1] = 3 coins
print(coinChangeGreedy(coins, amount))  # 3 (WRONG)

# Optimal: [3, 3] = 2 coins ✗

Correct DP solution:

python
def coinChange(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

# Example
print(coinChange([1, 3, 4], 6))  # 2: [3, 3] ✓

Why DP is needed: Greedy choice (largest coin) doesn't guarantee global optimum. Must explore all choices.

Time: O(amount × coins), Space: O(amount)

See detailed guide: Why Greedy Fails on Coin Change

Problem 2: 0/1 Knapsack

Problem: Maximize value in knapsack (can't take fractions).

Why greedy fails:

python
# WRONG: Greedy by value/weight ratio
def knapsackGreedy(values, weights, capacity):
    items = [(v/w, w, v, i) for i, (v, w) in enumerate(zip(values, weights))]
    items.sort(reverse=True)
    
    total_value = 0
    
    for ratio, weight, value, idx in items:
        if capacity >= weight:
            total_value += value
            capacity -= weight
    
    return total_value

# Counterexample
values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50

# Greedy: Pick item 0 (ratio=6), item 1 (ratio=5)
#   weight=30, value=160
print(knapsackGreedy(values, weights, capacity))  # 160 (WRONG)

# Optimal: Pick item 1 and item 2
#   weight=50, value=220 ✗

Correct DP solution:

python
def knapsack(values, weights, capacity):
    n = len(values)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            # Don't take item i-1
            dp[i][w] = dp[i-1][w]
            
            # Take item i-1 if it fits
            if weights[i-1] <= w:
                dp[i][w] = max(dp[i][w], 
                              dp[i-1][w - weights[i-1]] + values[i-1])
    
    return dp[n][capacity]

# Example
print(knapsack([60, 100, 120], [10, 20, 30], 50))  # 220 ✓

Why DP is needed: Can't take fractions, so greedy by ratio fails. Must explore all combinations.

Time: O(n × capacity), Space: O(n × capacity)

Problem 3: Longest Increasing Subsequence

Problem: Find length of longest increasing subsequence.

Why greedy fails:

python
# WRONG: Greedy approach (pick smallest next element)
def lisGreedy(nums):
    if not nums:
        return 0
    
    length = 1
    last = nums[0]
    
    for num in nums[1:]:
        if num > last:
            length += 1
            last = num
    
    return length

# Counterexample
nums = [10, 9, 2, 5, 3, 7, 101, 18]

# Greedy: [10, 101] = length 2 (picks 10, then 101)
print(lisGreedy(nums))  # 2 (WRONG)

# Optimal: [2, 3, 7, 101] or [2, 5, 7, 101] = length 4 ✗

Correct DP solution:

python
def lengthOfLIS(nums):
    if not nums:
        return 0
    
    dp = [1] * len(nums)
    
    for i in range(1, len(nums)):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    
    return max(dp)

# Example
print(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]))  # 4 ✓

Why DP is needed: Greedy choice of smallest next element misses longer subsequences. Must explore all possibilities.

Time: O(n²), Space: O(n)

The Counterexample Test

Always test greedy with these classic counterexamples:

Test 1: Coin Change

python
coins = [1, 3, 4], amount = 6
# Greedy: [4, 1, 1] = 3
# Optimal: [3, 3] = 2
# Verdict: Greedy FAILS ✗

Test 2: 0/1 Knapsack

python
values = [60, 100, 120], weights = [10, 20, 30], capacity = 50
# Greedy (ratio): items 0,1 → value=160
# Optimal: items 1,2 → value=220
# Verdict: Greedy FAILS ✗

Test 3: Interval Scheduling

python
intervals = [(1,3), (2,5), (4,7)]
# Greedy (end time): [(1,3), (4,7)] = 2
# Optimal: same = 2
# Verdict: Greedy WORKS ✓

Test 4: Jump Game

python
nums = [2, 3, 1, 1, 4]
# Greedy (max reach): reach=8 ≥ 4 → True
# Optimal: same → True
# Verdict: Greedy WORKS ✓

Hybrid Approaches

Sometimes you can combine greedy and DP:

Greedy Heuristic + DP Verification

python
def hybridApproach(problem):
    # Try greedy first (fast)
    greedy_result = greedySolution(problem)
    
    # Verify with DP (slow but correct)
    if needsVerification:
        dp_result = dpSolution(problem)
        return dp_result
    
    return greedy_result

Greedy Preprocessing + DP

python
def greedyThenDP(items):
    # Greedy: Sort to enable DP optimization
    items.sort(key=lambda x: x.value)
    
    # DP: Solve on sorted items
    return dpSolution(items)

Common Mistakes

Mistake 1: Assuming Greedy Always Works on Optimization

Wrong:

python
# "It's an optimization problem, so greedy should work"
def solve(problem):
    return greedySolution(problem)  # No proof!

Correct:

python
# Always test with counterexamples first
def solve(problem):
    # Test: Does greedy fail on known counterexamples?
    if hasCounterexample(problem):
        return dpSolution(problem)
    return greedySolution(problem)

Mistake 2: Not Testing Edge Cases

Wrong:

python
# Only test on simple inputs
coins = [1, 2, 5], amount = 11
# Greedy works here, so assume it always works

Correct:

python
# Test on adversarial inputs
coins = [1, 3, 4], amount = 6
# Greedy fails here, so use DP

Mistake 3: Using DP When Greedy Suffices

Wrong:

python
# Using O(n²) DP for interval scheduling
def maxIntervals(intervals):
    # Complex DP solution...
    # Time: O(n²), Space: O(n)

Correct:

python
# Using O(n log n) greedy
def maxIntervals(intervals):
    intervals.sort(key=lambda x: x[1])
    # Simple greedy solution...
    # Time: O(n log n), Space: O(1)

Quick Reference Table

ProblemGreedy Works?Why?
Interval Scheduling✅ YesEarliest end time is provably optimal
Fractional Knapsack✅ YesCan take fractions, ratio is optimal
Jump Game✅ YesMax reach is sufficient
Gas Station✅ YesCan't reach from i→j means can't from any i'→j
Activity Selection✅ YesSame as interval scheduling
Coin Change (arbitrary)❌ NoCounterexample: [1,3,4], amount=6
0/1 Knapsack❌ NoCan't take fractions, ratio fails
Longest Increasing Subsequence❌ NoGreedy misses longer subsequences
Edit Distance❌ NoNo greedy choice property
Partition Equal Subset Sum❌ NoNP-complete, need DP

Practice Strategy

To master the greedy vs DP distinction:

  1. Solve Coin Change (#322) with DP, try greedy, find counterexample
  2. Solve Jump Game (#55) with greedy, understand why it works
  3. Solve 0/1 Knapsack with DP, try greedy by ratio, find counterexample
  4. Solve Interval Scheduling (#435) with greedy, prove correctness
  5. Practice pattern recognition with LeetCopilot's Smart Context

FAQ

Q: How do I know if greedy choice property holds?

A: Try to find a counterexample. If you can't, try to prove it with exchange argument or induction. See Greedy Proof Techniques.

Q: Can I always use DP instead of greedy?

A: Yes, DP is more general, but greedy is faster when it works. Greedy is O(n) or O(n log n), DP is often O(n²) or worse.

Q: What if I'm not sure in an interview?

A: State both approaches: "Greedy might work if [condition], but to be safe, I'll use DP which is guaranteed correct." Then implement DP.

Q: Are there problems where both work?

A: Yes! Jump Game can be solved with both greedy (O(n)) and DP (O(n²)). Greedy is better when it works.

Conclusion

The greedy vs DP distinction is the most important decision in algorithm interviews.

Key principles:

  • Test with counterexamples before committing to greedy
  • Prove correctness if using greedy
  • Use DP when in doubt (safer, guaranteed correct)
  • Know the classic counterexamples (coin change, 0/1 knapsack)

The decision framework:

  1. Optimization problem? → Consider greedy
  2. Can identify greedy choice? → Test it
  3. Counterexample exists? → Use DP
  4. Can prove correctness? → Use greedy
  5. Unsure? → Use DP (safer)

When greedy works:

  • Interval scheduling (provably optimal)
  • Fractional knapsack (ratio is optimal)
  • Jump game (max reach suffices)

When you need DP:

  • Coin change (arbitrary denominations)
  • 0/1 knapsack (can't take fractions)
  • Longest increasing subsequence (no greedy choice)

Master this distinction, and you'll never submit a wrong greedy solution again. For specific patterns, see the complete greedy guide, Interval Scheduling Template, and Why Greedy Fails on Coin Change.

Next time you see an optimization problem, ask: "Does greedy choice property hold?" Test it. Prove it. Or use DP.

Want to Practice LeetCode Smarter?

LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.

Also compatible with Edge, Brave, and Opera

Related Tutorials