Using Greedy Algorithms
1. Applying Greedy Choice Property
| Property | Description | Application |
|---|---|---|
| Locally Optimal Choice | At each step, make the choice that looks best at the moment | Used in algorithms like Huffman coding, activity selection |
| Global Optimization | Assumes that local choices lead to a globally optimal solution | Applicable in problems where greedy approach guarantees best result |
| No Backtracking | Once a choice is made, it's never reconsidered | Used in Dijkstra’s shortest path, Kruskal’s MST |
Example: Activity Selection Problem
const activities = [
{ name: 'A1', start: 0, end: 6 },
{ name: 'A2', start: 3, end: 4 },
{ name: 'A3', start: 1, end: 2 },
{ name: 'A4', start: 5, end: 7 },
{ name: 'A5', start: 8, end: 9 },
{ name: 'A6', start: 5, end: 9 }
];
function selectActivities(activities) {
// Sort by finish time
activities.sort((a, b) => a.end - b.end);
const selected = [activities[0]];
for (let i = 1; i < activities.length; i++) {
if (activities[i].start >= selected[selected.length - 1].end) {
selected.push(activities[i]);
}
}
return selected;
}
console.log(selectActivities(activities));
// Output: [{ name: 'A3', start: 1, end: 2 }, { name: 'A2', start: 3, end: 4 }, { name: 'A4', start: 5, end: 7 }, { name: 'A5', start: 8, end: 9 }]
2. Solving Activity Selection
| Property | Description | Usage |
|---|---|---|
| Activity | An event with a start time and end time | Represented as [start, end] |
| Activity Set | Collection of activities with start and end times | Input for greedy selection algorithm |
| Compatible Activities | Activities that do not overlap in time | Selected to maximize count without conflict |
Example: Selecting Non-Overlapping Activities
const activities = [
[1, 4],
[3, 5],
[0, 6],
[5, 7],
[3, 9],
[5, 9],
[6, 10],
[8, 11],
[8, 12],
[2, 13],
[12, 14]
];
function activitySelection(activities) {
// Sort by end time
activities.sort((a, b) => a[1] - b[1]);
const selected = [activities[0]];
let lastEnd = activities[0][1];
for (let i = 1; i < activities.length; i++) {
const [start, end] = activities[i];
if (start >= lastEnd) {
selected.push(activities[i]);
lastEnd = end;
}
}
return selected;
}
console.log(activitySelection(activities));
// Output: [[1, 4], [5, 7], [8, 11], [12, 14]]
Key Points
- Select activities based on earliest finish time
- Sort all activities by end time
- Iterate and pick compatible ones
- Time complexity:
O(n log n)due to sorting
3. Solving Fractional Knapsack
The Fractional Knapsack problem involves selecting items with given weights and values to maximize the total value within a weight capacity. Unlike the 0/1 Knapsack problem, items can be broken into fractions.
| Property | Description | Value |
|---|---|---|
| Problem Type | Optimization problem | Maximize value within weight constraint |
| Approach | Greedy algorithm | Sort items by value-to-weight ratio |
| Time Complexity | Sorting + iteration | O(n log n) |
| Space Complexity | Additional space for sorting | O(1) |
Example: Solving Fractional Knapsack
function fractionalKnapsack(capacity, weights, values) {
const n = weights.length;
const items = [];
for (let i = 0; i < n; i++) {
items.push({
value: values[i],
weight: weights[i],
ratio: values[i] / weights[i]
});
}
items.sort((a, b) => b.ratio - a.ratio);
let totalValue = 0;
let remainingCapacity = capacity;
for (const item of items) {
if (item.weight <= remainingCapacity) {
totalValue += item.value;
remainingCapacity -= item.weight;
} else {
const fraction = remainingCapacity / item.weight;
totalValue += item.value * fraction;
break;
}
}
return totalValue;
}
// Example usage:
const capacity = 50;
const weights = [10, 20, 30];
const values = [60, 100, 120];
console.log(fractionalKnapsack(capacity, weights, values)); // Output: 240
4. Solving Jump Game
| Property | Description | Example |
|---|---|---|
| jumpGame | Function to determine if you can jump to the end of the array | jumpGame([2,3,1,1,4]) |
| maxReach | Maximum index reachable at any point | maxReach = Math.max(maxReach, i + nums[i]) |
Example: Jump Game Solution
function jumpGame(nums) {
let maxReach = 0;
for (let i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
}
5. Solving Jump Game II
The Jump Game II problem involves finding the minimum number of jumps needed to reach the end of an array, where each element represents the maximum jump length from that position.
| Property | Description | Value |
|---|---|---|
| Input | Array of integers where each element represents the maximum jump length | [2, 3, 1, 1, 4] |
| Output | Minimum number of jumps to reach the end | 2 |
Example: Jump Game II Solution
function jump(nums) {
let jumps = 0;
let currentEnd = 0;
let farthest = 0;
for (let i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i === currentEnd) {
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
// Example usage:
const nums = [2, 3, 1, 1, 4];
console.log(jump(nums)); // Output: 2
6. Solving Gas Station
The Gas Station problem involves determining the starting gas station from which a car can complete a circular route. The car has an unlimited capacity tank and starts with an empty tank.
| Property | Description | Notes |
|---|---|---|
| gas | Array of integers representing the amount of gas available at each station. | Length is equal to the number of stations. |
| cost | Array of integers representing the cost (in gas) to travel from each station to the next. | Length is equal to the number of stations. |
| start | Index of the starting station that allows completing the circuit. | Returns -1 if no such station exists. |
Example: Gas Station Problem
function canCompleteCircuit(gas, cost) {
let totalGas = 0;
let currentGas = 0;
let startStation = 0;
for (let i = 0; i < gas.length; i++) {
totalGas += gas[i] - cost[i];
currentGas += gas[i] - cost[i];
if (currentGas < 0) {
startStation = i + 1;
currentGas = 0;
}
}
return totalGas >= 0 ? startStation : -1;
}
// Example usage:
const gas = [1, 2, 3, 4, 5];
const cost = [3, 4, 5, 1, 2];
console.log(canCompleteCircuit(gas, cost)); // Output: 3
7. Solving Task Scheduler
The Task Scheduler problem involves scheduling tasks with cooldown periods to minimize the total time needed to complete all tasks.
| Property | Description | Example |
|---|---|---|
| Task | Each task has a name and frequency | A: 3, B: 2, C: 1 |
| Cool Down Period | Minimum time between same tasks | 2 |
| Result | Minimum time to complete all tasks | 8 |
Example: Task Scheduler Implementation
function leastInterval(tasks, n) {
const taskCount = {};
for (const task of tasks) {
taskCount[task] = (taskCount[task] || 0) + 1;
}
const maxHeap = new MaxHeap();
Object.values(taskCount).forEach(count => maxHeap.insert(count));
let time = 0;
const queue = [];
while (!maxHeap.isEmpty() || queue.length > 0) {
time++;
if (!maxHeap.isEmpty()) {
const count = maxHeap.extractMax() - 1;
queue.push({ count, time: time + n });
}
if (queue.length > 0 && queue[0].time === time) {
const task = queue.shift();
if (task.count > 0) maxHeap.insert(task.count);
}
}
return time;
}
class MaxHeap {
constructor() {
this.heap = [];
}
insert(val) {
this.heap.push(val);
this.bubbleUp(this.heap.length - 1);
}
bubbleUp(index) {
if (index === 0) return;
const parentIndex = Math.floor((index - 1) / 2);
if (this.heap[parentIndex] < this.heap[index]) {
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
this.bubbleUp(parentIndex);
}
}
extractMax() {
if (this.heap.length === 0) return null;
const max = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.sinkDown(0);
}
return max;
}
sinkDown(index) {
const left = 2 * index + 1;
const right = 2 * index + 2;
let maxIndex = index;
if (left < this.heap.length && this.heap[left] > this.heap[maxIndex]) {
maxIndex = left;
}
if (right < this.heap.length && this.heap[right] > this.heap[maxIndex]) {
maxIndex = right;
}
if (maxIndex !== index) {
[this.heap[index], this.heap[maxIndex]] = [this.heap[maxIndex], this.heap[index]];
this.sinkDown(maxIndex);
}
}
isEmpty() {
return this.heap.length === 0;
}
}
// Example usage:
const tasks = ["A", "A", "A", "B", "B", "B"];
const n = 2;
console.log(leastInterval(tasks, n)); // Output: 8
8. Solving Partition Labels
Partition labels is a classic greedy algorithm problem where we need to partition a string into as many parts as possible such that each letter appears in only one part.
| Property | Description | Example |
|---|---|---|
| Input | String to be partitioned | "ababcbacadefegdehijhklij" |
| Output | List of partition sizes | [9, 7, 8] |
Example: Partition Labels
function partitionLabels(s) {
const last = {};
for (let i = 0; i < s.length; i++) {
last[s[i]] = i;
}
const result = [];
let start = 0, end = 0;
for (let i = 0; i < s.length; i++) {
end = Math.max(end, last[s[i]]);
if (i === end) {
result.push(end - start + 1);
start = end + 1;
}
}
return result;
}
// Example usage:
console.log(partitionLabels("ababcbacadefegdehijhklij")); // [9, 7, 8]
9. Solving Meeting Rooms II
Given an array of meeting time intervals where intervals[i] = [start<sub>i</sub>, end<sub>i</sub>], return the minimum number of conference rooms required.
| Input | Description | Example |
|---|---|---|
| intervals | Array of intervals, each interval is [start, end] | [[0,30],[5,10],[15,20]] |
Example 1:
Input: [[0,30],[5,10],[15,20]]
Output: 2
Explanation: One room can host [0,30], another [5,10] and [15,20].
Approach: Use a min-heap to track the end times of meetings. For each meeting, if the earliest ending meeting has ended before the current one starts, reuse that room. Otherwise, allocate a new room.
| Step | Action | Heap State |
|---|---|---|
| 1 | Sort intervals by start time | [0,30] |
| 2 | Push 30 to heap |
[30] |
| 3 | Compare 5 with top of heap 30 |
[10, 30] |
| 4 | Compare 15 with top of heap 10 |
[20, 30] |
function minMeetingRooms(intervals) {
if (intervals.length === 0) return 0;
intervals.sort((a, b) => a[0] - b[0]);
const minHeap = new MinHeap();
minHeap.push(intervals[0][1]);
for (let i = 1; i < intervals.length; i++) {
const currentStart = intervals[i][0];
const earliestEnd = minHeap.peek();
if (currentStart >= earliestEnd) {
minHeap.pop(); // Reuse room
}
minHeap.push(intervals[i][1]);
}
return minHeap.size();
}
class MinHeap {
constructor() {
this.heap = [];
}
push(val) {
this.heap.push(val);
this.bubbleUp(this.heap.length - 1);
}
pop() {
if (this.heap.length === 0) return null;
const min = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.bubbleDown(0);
}
return min;
}
peek() {
return this.heap[0];
}
size() {
return this.heap.length;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
if (this.heap[parentIndex] <= this.heap[index]) break;
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
index = parentIndex;
}
}
bubbleDown(index) {
const len = this.heap.length;
while (true) {
const leftChild = 2 * index + 1;
const rightChild = 2 * index + 2;
let minIndex = index;
if (leftChild < len && this.heap[leftChild] < this.heap[minIndex]) {
minIndex = leftChild;
}
if (rightChild < len && this.heap[rightChild] < this.heap[minIndex]) {
minIndex = rightChild;
}
if (minIndex === index) break;
[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];
index = minIndex;
}
}
}
10. Solving Remove K Digits
Given a non-negative integer represented as a string num, remove k digits to make the resulting number as small as possible.
| Property | Description | Example |
|---|---|---|
| Input | String representation of a non-negative integer | "1432219" |
| k | Number of digits to remove | 3 |
| Output | Smallest possible integer after removing k digits |
"1221" |
Example: Remove K Digits
function removeKdigits(num, k) {
const stack = [];
const toRemove = k;
for (let i = 0; i < num.length; i++) {
const digit = num[i];
while (stack.length > 0 && stack[stack.length - 1] > digit && toRemove > 0) {
stack.pop();
toRemove--;
}
if (stack.length > 0 || digit !== '0') {
stack.push(digit);
}
}
while (toRemove > 0) {
stack.pop();
toRemove--;
}
return stack.length === 0 ? "0" : stack.join("");
}
// Example usage:
console.log(removeKdigits("1432219", 3)); // "1221"
console.log(removeKdigits("10200", 1)); // "200"
console.log(removeKdigits("10", 2)); // "0"
11. Solving Candy Distribution
Given a list of children's ratings, distribute candies such that:
- Each child gets at least one candy.
- A child with a higher rating than their neighbor(s) gets more candies.
| Property | Description | Example |
|---|---|---|
| ratings | List of integers representing children's ratings | [1, 0, 2] |
| candies | Minimum number of candies to distribute | 5 |
Example: Candy Distribution
function candy(ratings) {
const n = ratings.length;
const candies = new Array(n).fill(1);
// Left to right pass
for (let i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
// Right to left 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((sum, count) => sum + count, 0);
}
// Example usage
const ratings = [1, 0, 2];
console.log(candy(ratings)); // Output: 5
12. Solving Minimum Arrows
Given a set of intervals on the number line, the goal is to determine the minimum number of arrows needed to shoot all intervals. This problem can be solved efficiently using a greedy algorithm by sorting the intervals and selecting the optimal arrow positions.
| Step | Description | Action |
|---|---|---|
| 1 | Sort intervals by their end points | Arrange all intervals in ascending order of their end points. |
| 2 | Initialize arrow count and last arrow position | Start with 1 arrow and set the position to the end of the first interval. |
| 3 | Iterate through sorted intervals | For each interval, if its start is greater than the last arrow position, increment arrow count and update arrow position. |
Example: Minimum Arrows to Shoot Balloons
const findMinArrowShots = (points) => {
if (points.length === 0) return 0;
// Sort intervals by end point
points.sort((a, b) => a[1] - b[1]);
let arrows = 1;
let lastArrowPosition = points[0][1];
for (let i = 1; i < points.length; i++) {
const [start, end] = points[i];
if (start > lastArrowPosition) {
arrows++;
lastArrowPosition = end;
}
}
return arrows;
};
// Example usage:
const points = [[10,16], [2,8], [1,6], [7,12]];
console.log(findMinArrowShots(points)); // Output: 2