A Greedy Algorithm makes the locally optimal choice at each step, hoping to find the global optimum. It never reconsiders previous choices.
// Greedy works when:
// 1. Greedy choice property — local optimal leads to global optimal
// 2. Optimal substructure — optimal solution contains optimal sub-solutions
// Example: giving change with fewest coins (standard denominations)
// For amount 63 with [25, 10, 5, 1]:
// Take 25, 25 (50), then 10 (60), then 1, 1, 1 (63) => 6 coins
Greedy doesn't always work (e.g., coin change with arbitrary denominations may need DP).
Select the maximum number of non-overlapping activities. Greedy: sort by finish time and always pick the earliest finishing activity.
function activitySelection(start, finish) {
const n = start.length;
const activities = start.map((s, i) => [s, finish[i]])
.sort((a, b) => a[1] - b[1]);
const result = [activities[0]];
let lastEnd = activities[0][1];
for (let i = 1; i < n; i++) {
if (activities[i][0] >= lastEnd) {
result.push(activities[i]);
lastEnd = activities[i][1];
}
}
return result;
}
Time: O(n log n) for sorting | Space: O(n)
Unlike 0/1 knapsack, you can take fractions of items. Greedy: sort by value/weight ratio and take items greedily.
function fractionalKnapsack(items, capacity) {
items.sort((a, b) => b.value/b.weight - a.value/a.weight);
let totalValue = 0;
for (const item of items) {
if (capacity >= item.weight) {
totalValue += item.value;
capacity -= item.weight;
} else {
totalValue += (capacity / item.weight) * item.value;
break;
}
}
return totalValue;
}
Time: O(n log n) | Greedy works here because we can take fractions.
Given jobs with deadlines and profits, schedule jobs to maximize profit. Each job takes one unit of time. Greedy: sort by profit descending and assign to the latest available slot before deadline.
function jobSequencing(jobs) {
jobs.sort((a, b) => b.profit - a.profit);
const maxDeadline = Math.max(...jobs.map(j => j.deadline));
const slots = new Array(maxDeadline).fill(null);
let totalProfit = 0;
for (const job of jobs) {
for (let t = job.deadline - 1; t >= 0; t--) {
if (slots[t] === null) {
slots[t] = job.id;
totalProfit += job.profit;
break;
}
}
}
return { slots, totalProfit };
}
Time: O(n²) or O(n log n) with union-find
Huffman Coding is a greedy algorithm for lossless data compression. It assigns shorter codes to more frequent characters using a priority queue (min-heap).
// Algorithm:
// 1. Count frequency of each character
// 2. Create a leaf node for each character
// 3. While more than one node in the queue:
// a. Extract two nodes with lowest frequency
// b. Create a new internal node with their sum
// c. Insert the new node back
// 4. The remaining node is the root
// Example: "aabbbcccc"
// c:4, b:3, a:2
// Tree assigns: c=0, b=10, a=11
// Compressed: fewer bits than fixed-length encoding
Time: O(n log n) with heap | Produces optimal prefix codes.
For standard coin denominations (e.g., 1, 5, 10, 25), greedy works: always pick the largest coin that fits.
function minCoinsGreedy(coins, amount) {
coins.sort((a, b) => b - a); // sort descending
let count = 0;
const used = [];
for (const coin of coins) {
while (amount >= coin) {
amount -= coin;
used.push(coin);
count++;
}
}
return { count, used };
}
// minCoinsGreedy([1, 5, 10, 25], 63)
// => { count: 6, used: [25, 25, 10, 1, 1, 1] }
Note: Greedy fails for arbitrary denominations (e.g., [1, 3, 4] for amount 6). Use DP for those cases.
Given meeting start and end times, find the maximum number of meetings in one room. Same as activity selection: sort by end time and greedily pick non-overlapping meetings.
function maxMeetings(start, end) {
const meetings = start.map((s, i) => ({s, e: end[i], idx: i + 1}));
meetings.sort((a, b) => a.e - b.e);
const result = [meetings[0].idx];
let lastEnd = meetings[0].e;
for (let i = 1; i < meetings.length; i++) {
if (meetings[i].s > lastEnd) {
result.push(meetings[i].idx);
lastEnd = meetings[i].e;
}
}
return result;
}
Time: O(n log n) | Space: O(n)
Find the minimum number of platforms needed at a railway station so no train waits. Sort arrivals and departures separately and use the two-pointer technique.
function minPlatforms(arrivals, departures) {
arrivals.sort((a, b) => a - b);
departures.sort((a, b) => a - b);
let platforms = 0, maxPlatforms = 0;
let i = 0, j = 0;
while (i < arrivals.length) {
if (arrivals[i] <= departures[j]) {
platforms++;
i++;
} else {
platforms--;
j++;
}
maxPlatforms = Math.max(maxPlatforms, platforms);
}
return maxPlatforms;
}
// minPlatforms([900,940,950,1100], [910,1200,1120,1130]) => 3
Time: O(n log n) | Space: O(1)
Given an array where each element is the max jump length, determine if you can reach the last index. Greedy: track the farthest reachable position.
function canJump(nums) {
let farthest = 0;
for (let i = 0; i < nums.length; i++) {
if (i > farthest) return false;
farthest = Math.max(farthest, i + nums[i]);
}
return true;
}
// canJump([2,3,1,1,4]) => true
// canJump([3,2,1,0,4]) => false
Time: O(n) | Space: O(1)
There are n gas stations in a circle. Find the starting station index to complete the circuit, or return -1. Greedy: if total gas ≥ total cost, a solution exists. Track the current deficit to find the start.
function canCompleteCircuit(gas, cost) {
let totalSurplus = 0, currentSurplus = 0, start = 0;
for (let i = 0; i < gas.length; i++) {
totalSurplus += gas[i] - cost[i];
currentSurplus += gas[i] - cost[i];
if (currentSurplus < 0) {
start = i + 1;
currentSurplus = 0;
}
}
return totalSurplus >= 0 ? start : -1;
}
// canCompleteCircuit([1,2,3,4,5], [3,4,5,1,2]) => 3
Time: O(n) | Space: O(1)
Greedily extend your reach: track current reach and next farthest reach. Each time you exhaust current reach, increment jump count and advance to next farthest.
function jump(nums) {
let jumps = 0, currEnd = 0, farthest = 0;
for (let i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i === currEnd) { // reached current boundary
jumps++;
currEnd = farthest;
}
}
return jumps;
}
// Input: [2,3,1,1,4] => Output: 2 (0->1->4 or 0->4 or ... min=2 jumps)
// Input: [2,3,0,1,4] => Output: 2
// Input: [1,2,3] => Output: 2 (0->1->2)
Time: O(n) | Space: O(1)
Sort meetings by start time. Use a min-heap of end times. For each meeting, if it starts after the earliest ending meeting's end, reuse that room. Otherwise, add a room.
function minMeetingRooms(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
const endTimes = []; // simulated min-heap
for (const [start, end] of intervals) {
endTimes.sort((a, b) => a - b);
if (endTimes.length && endTimes[0] <= start) {
endTimes.shift(); // reuse room
}
endTimes.push(end); // allocate room
}
return endTimes.length;
}
// Input: [[0,30],[5,10],[15,20]] => Output: 2
// Input: [[7,10],[2,4]] => Output: 1 (no overlap)
// Input: [[1,5],[2,6],[3,7]] => Output: 3 (all overlap)
Time: O(n log n) | Space: O(n)
Sort by end position. A single arrow can burst all balloons whose start ≤ current arrow position. Shoot at the end of the first balloon and skip all that overlap.
function findMinArrowShots(points) {
points.sort((a, b) => a[1] - b[1]); // sort by end position
let arrows = 1, arrowPos = points[0][1];
for (let i = 1; i < points.length; i++) {
if (points[i][0] > arrowPos) { // balloon starts after last arrow
arrows++;
arrowPos = points[i][1]; // shoot at end of this balloon
}
}
return arrows;
}
// Input: [[10,16],[2,8],[1,6],[7,12]] => Output: 2
// Input: [[1,2],[3,4],[5,6],[7,8]] => Output: 4
// Input: [[1,2],[2,3],[3,4],[4,5]] => Output: 2
Time: O(n log n) | Space: O(1)
Custom sort: compare two numbers a and b by checking which concatenation (ab vs ba) is larger. Use string comparison.
function largestNumber(nums) {
const sorted = nums.map(String).sort((a, b) => {
return (b + a).localeCompare(a + b); // or compare (b+a) vs (a+b)
});
// Edge case: all zeros
if (sorted[0] === '0') return '0';
return sorted.join('');
}
// Input: [10,2] => Output: "210" (210 > 102)
// Input: [3,30,34,5,9] => Output: "9534330"
// Input: [0,0] => Output: "0"
Time: O(n log n × m) where m is average digit length | Space: O(n)
Sort events by end day. Use a greedy approach: for each available day, attend the event ending earliest (use a min-heap of end times).
function maxEvents(events) {
events.sort((a, b) => a[0] - b[0]); // sort by start day
const endHeap = []; // min-heap of end days
let day = 0, count = 0, i = 0;
const n = events.length;
while (i < n || endHeap.length) {
if (!endHeap.length) day = events[i][0]; // jump to next event start
// Add all events that start on or before today
while (i < n && events[i][0] <= day) {
endHeap.push(events[i][1]);
endHeap.sort((a, b) => a - b);
i++;
}
// Attend earliest-ending available event
while (endHeap.length && endHeap[0] < day) endHeap.shift(); // expired
if (endHeap.length) { endHeap.shift(); count++; }
day++;
}
return count;
}
// Input: [[1,2],[2,3],[3,4]] => Output: 3
// Input: [[1,4],[4,4],[2,2],[3,4],[1,1]] => Output: 4
Time: O(n log n) | Space: O(n)
Sort both children's greed factors and cookie sizes. Use a two-pointer greedy: give the smallest sufficient cookie to the least greedy child.
function findContentChildren(g, s) {
g.sort((a, b) => a - b); // greed factors
s.sort((a, b) => a - b); // cookie sizes
let child = 0, cookie = 0;
while (child < g.length && cookie < s.length) {
if (s[cookie] >= g[child]) child++; // cookie satisfies child
cookie++; // try next cookie either way
}
return child; // number of satisfied children
}
// Input: g=[1,2,3], s=[1,1] => Output: 1
// Input: g=[1,2], s=[1,2,3] => Output: 2
// Input: g=[10,9,8], s=[1,2] => Output: 0
Time: O(n log n + m log m) | Space: O(1)
Sort workers by ratio (wage/quality). For each worker considered as the highest-paid (sets the ratio), sum the k smallest qualities. Use a max-heap to maintain the k smallest.
function mincostToHireWorkers(quality, wage, k) {
const workers = quality.map((q, i) => [wage[i] / q, q]); // [ratio, quality]
workers.sort((a, b) => a[0] - b[0]); // sort by ratio
let totalQ = 0, minCost = Infinity;
const maxHeap = []; // track k smallest qualities
for (const [ratio, q] of workers) {
maxHeap.push(q);
maxHeap.sort((a, b) => b - a); // max at front
totalQ += q;
if (maxHeap.length > k) totalQ -= maxHeap.shift(); // remove largest quality
if (maxHeap.length === k) minCost = Math.min(minCost, ratio * totalQ);
}
return minCost;
}
// Input: quality=[10,20,5], wage=[70,50,30], k=2 => Output: 105.00
Time: O(n log n) | Space: O(k)
Convert taps to intervals, then apply the jump game greedy: find the farthest point reachable from any position in the current window.
function minTaps(n, ranges) {
const maxReach = new Array(n + 1).fill(0);
for (let i = 0; i <= n; i++) {
const left = Math.max(0, i - ranges[i]);
const right = Math.min(n, i + ranges[i]);
maxReach[left] = Math.max(maxReach[left], right);
}
let taps = 0, currEnd = 0, farthest = 0;
for (let i = 0; i <= n; i++) {
if (i > farthest) return -1; // garden can't be watered
farthest = Math.max(farthest, maxReach[i]);
if (i === currEnd && i < n) { taps++; currEnd = farthest; }
}
return taps;
}
// Input: n=5, ranges=[3,4,1,1,0,0] => Output: 1 (tap 0 or 1 covers all)
// Input: n=7, ranges=[1,2,1,0,2,1,0,1] => Output: 3
Time: O(n) | Space: O(n)
To minimize the makespan (max completion time), assign tasks in non-increasing order of processing time to processors using the Longest Processing Time (LPT) rule.
function assignTasks(tasks, workers) {
tasks.sort((a, b) => b - a); // sort descending
const loads = new Array(workers).fill(0);
for (const task of tasks) {
loads.sort((a, b) => a - b); // assign to least loaded worker
loads[0] += task;
}
return Math.max(...loads); // makespan
}
// Input: tasks=[3,2,3,2], workers=2 => Output: 5 (3+2=5 each)
// Input: tasks=[1,2,4,8], workers=2 => Output: 9 (8+1=9 vs 4+2=6)
Time: O(n log n) | Space: O(workers)
Track the minimum buy price (adjusted for fee). For each day, if selling now is profitable (price > minBuy), take the profit. Update minBuy greedily.
function maxProfitWithFee(prices, fee) {
let cash = 0; // profit when not holding stock
let hold = -prices[0]; // profit when holding stock
for (let i = 1; i < prices.length; i++) {
cash = Math.max(cash, hold + prices[i] - fee); // sell today
hold = Math.max(hold, cash - prices[i]); // buy today
}
return cash;
}
// Input: prices=[1,3,2,8,4,9], fee=2 => Output: 8
// (buy@1 sell@8 fee=2 => profit=5; buy@4 sell@9 fee=2 => profit=3; total=8)
// Input: prices=[1,3,7,5,10,3], fee=3 => Output: 6
Time: O(n) | Space: O(1)
Give everyone 1 candy. Pass left-to-right: if rating[i] > rating[i-1], give one more than leftNeighbor. Pass right-to-left: if rating[i] > rating[i+1], ensure right side also satisfied.
function candy(ratings) {
const n = ratings.length;
const candies = new Array(n).fill(1);
// Left pass
for (let i = 1; i < n; i++) {
if (ratings[i] > ratings[i-1]) candies[i] = candies[i-1] + 1;
}
// Right pass
for (let i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i+1]) candies[i] = Math.max(candies[i], candies[i+1] + 1);
}
return candies.reduce((a, b) => a + b, 0);
}
// Input: [1,0,2] => Output: 5 ([2,1,2])
// Input: [1,2,2] => Output: 4 ([1,2,1])
// Input: [1,3,2,2,1] => Output: 7 ([1,3,1,2,1])
Time: O(n) | Space: O(n)
Compute value-to-weight ratio for each item. Sort by ratio descending. Take as much as possible from the highest-ratio item (fractions allowed).
function fractionalKnapsack(capacity, weights, values) {
const items = values.map((v, i) => [v / weights[i], weights[i], v]);
items.sort((a, b) => b[0] - a[0]); // sort by ratio descending
let totalValue = 0;
for (const [ratio, w, v] of items) {
if (capacity >= w) {
totalValue += v;
capacity -= w;
} else {
totalValue += ratio * capacity; // take fraction
break;
}
}
return totalValue;
}
// Input: capacity=50, weights=[10,20,30], values=[60,100,120]
// Output: 240.0 (all of item 1 and 2, then 2/3 of item 3)
Time: O(n log n) | Space: O(n)