Skip to main content

Greedy Algorithms

Why Greedy Algorithms Matter​

Greedy algorithms make locally optimal choices at each step—often leading to globally optimal solutions:

  • Interval scheduling: Maximize number of non-overlapping tasks
  • Huffman coding: Optimal data compression
  • Shortest path: Dijkstra's algorithm
  • Minimum spanning tree: Prim's and Kruskal's algorithms

Real-world impact:

  • Activity selection: Greedy picks earliest finishing time → O(n log n) vs O(2ⁿ) brute force
  • Huffman coding: 20-50% compression for text files
  • Change making: Optimal for US coins (greedy works), fails for some coin systems

When greedy works:

  1. Greedy choice property: Local optimal leads to global optimal
  2. Optimal substructure: Optimal solution contains optimal solutions to subproblems

Core Concepts​

Greedy Strategy​

At each step, make the choice that looks best at the moment:

Classic Greedy Problems​

ProblemGreedy ChoiceTime
Activity SelectionEarliest finish timeO(n log n)
Fractional KnapsackHighest value/weightO(n log n)
Huffman CodingMerge smallest frequenciesO(n log n)
Minimum Spanning TreeLightest edgeO(E log V)

Greedy vs Dynamic Programming​

AspectGreedyDP
DecisionLocal optimalExplore all options
SpeedFaster (usually O(n log n))Slower (polynomial)
OptimalityNot always guaranteedAlways guaranteed
BacktrackingNoYes

Deep Dive​

Activity Selection​

Select max number of non-overlapping activities:

public int activitySelection(int[] start, int[] end) {
int n = start.length;
int[][] activities = new int[n][2];

for (int i = 0; i < n; i++) {
activities[i] = new int[]{start[i], end[i]};
}

// Sort by end time
Arrays.sort(activities, (a, b) -> a[1] - b[1]);

int count = 1;
int lastEnd = activities[0][1];

for (int i = 1; i < n; i++) {
if (activities[i][0] >= lastEnd) {
count++;
lastEnd = activities[i][1];
}
}

return count;
}

Why it works: Picking earliest finish time leaves maximum room for remaining activities

Fractional Knapsack​

class Item {
int value, weight;

double getValuePerWeight() {
return (double) value / weight;
}
}

public double fractionalKnapsack(Item[] items, int capacity) {
// Sort by value/weight descending
Arrays.sort(items, (a, b) ->
Double.compare(b.getValuePerWeight(), a.getValuePerWeight()));

double totalValue = 0;

for (Item item : items) {
if (capacity >= item.weight) {
// Take entire item
totalValue += item.value;
capacity -= item.weight;
} else {
// Take fraction of item
totalValue += item.getValuePerWeight() * capacity;
break;
}
}

return totalValue;
}

Greedy choice: Take item with highest value/weight ratio

Huffman Coding​

Build optimal prefix code for compression:

class HuffmanNode implements Comparable<HuffmanNode> {
char character;
int frequency;
HuffmanNode left, right;

boolean isLeaf() { return left == null && right == null; }

@Override
public int compareTo(HuffmanNode other) {
return this.frequency - other.frequency;
}
}

public Map<Character, String> huffmanCoding(String text) {
// Count frequencies
Map<Character, Integer> freq = new HashMap<>();
for (char c : text.toCharArray()) {
freq.merge(c, 1, Integer::sum);
}

// Build priority queue
PriorityQueue<HuffmanNode> pq = new PriorityQueue<>();
for (Map.Entry<Character, Integer> entry : freq.entrySet()) {
HuffmanNode node = new HuffmanNode();
node.character = entry.getKey();
node.frequency = entry.getValue();
pq.offer(node);
}

// Build tree
while (pq.size() > 1) {
HuffmanNode left = pq.poll();
HuffmanNode right = pq.poll();

HuffmanNode parent = new HuffmanNode();
parent.frequency = left.frequency + right.frequency;
parent.left = left;
parent.right = right;
pq.offer(parent);
}

// Generate codes
Map<Character, String> codes = new HashMap<>();
buildCodes(pq.peek(), "", codes);
return codes;
}

private void buildCodes(HuffmanNode node, String code,
Map<Character, String> codes) {
if (node.isLeaf()) {
codes.put(node.character, code.isEmpty() ? "0" : code);
return;
}
buildCodes(node.left, code + "0", codes);
buildCodes(node.right, code + "1", codes);
}

Practical Applications​

Minimum Coins for Change​

public int minCoins(int[] coins, int amount) {
Arrays.sort(coins);
int count = 0;

for (int i = coins.length - 1; i >= 0; i--) {
if (coins[i] <= amount) {
int num = amount / coins[i];
count += num;
amount -= num * coins[i];
}
if (amount == 0) break;
}

return amount == 0 ? count : -1;
}

Note: Greedy doesn't work for all coin systems (e.g., coins = [1, 3, 4], amount = 6)

Jump Game​

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;
}

Interview Questions​

Q1: Assign Cookies (Easy)​

Problem: Maximize children content with cookies.

Approach: Greedy match smallest cookie that satisfies each child

Complexity: O(n log n + m log m) time

public int findContentChildren(int[] greed, int[] size) {
Arrays.sort(greed);
Arrays.sort(size);

int child = 0, cookie = 0;

while (child < greed.length && cookie < size.length) {
if (size[cookie] >= greed[child]) {
child++;
}
cookie++;
}

return child;
}

Q2: Gas Station (Medium)​

Problem: Find starting gas station to complete circuit.

Approach: Greedy with total gas check

Complexity: O(n) time

public int canCompleteCircuit(int[] gas, int[] cost) {
int totalGas = 0, totalCost = 0;
int currentGas = 0, start = 0;

for (int i = 0; i < gas.length; i++) {
totalGas += gas[i];
totalCost += cost[i];

currentGas += gas[i] - cost[i];

if (currentGas < 0) {
start = i + 1;
currentGas = 0;
}
}

return totalGas >= totalCost ? start : -1;
}

Further Reading​

  • DP: For problems where greedy fails
  • Heaps: Used in greedy algorithms (Huffman)
  • Greedy: Interval scheduling, MST
  • LeetCode: Greedy problems