Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 89

Greedy Algorithms: Activity Selection & Fractional Knapsack

Recommended resources

Hello! Let's get started with our next topic.

In our last lesson, we tackled the Matrix Chain Multiplication problem using Dynamic Programming. You saw how DP meticulously explores every possibility to guarantee a globally optimal solution. Today, we shift our focus to a different, often simpler and faster algorithmic paradigm: Greedy Algorithms.

Your learning outcome for this lesson is to apply the greedy approach to solve the Activity Selection and Fractional Knapsack problems. We'll explore the core idea of making a "greedy choice"—a decision that looks best at the current moment—and, crucially, understand for which specific problems this local optimality leads to a global optimum. This is a vital concept for both technical interviews and competitive programming.

1. The Greedy Paradigm: A New Way of Thinking

Dynamic Programming finds the optimal solution by solving all subproblems and combining their results. Greedy algorithms take a more direct path. At each step, they make a choice that seems best at that moment, without worrying about future consequences.

This might sound shortsighted, and indeed, for many problems (like the 0/1 Knapsack or Matrix Chain Multiplication), it fails. However, for a special class of problems, making the locally optimal choice at each step is proven to lead to the globally optimal solution. The art of using greedy algorithms lies in identifying these problems and proving that the greedy strategy works.

To get a feel for this distinction, let's watch a short introduction.

Activity Selection Problem using Greedy Method | Maximum Disjoint Intervals | DSA-One Course #96

This video from the 'DSA-One Course' by Anuj Kumar Sharma provides an excellent intuitive comparison between Dynamic Programming and Greedy algorithms.

Watch from the beginning to 02:15. Focus on the explanation of how DP explores all paths, while a greedy algorithm commits to a single path based on a locally optimal decision.

The main takeaway is that a greedy algorithm bets on a specific heuristic. Our job is to find the right heuristic for the problem at hand. Let's see this in action with our first problem.


2. Activity Selection Problem

This is a classic problem that demonstrates the power of a correct greedy choice.

Problem Statement

You are given n activities, each with a start time and a finish time. You need to select the maximum number of activities that can be performed by a single person, knowing that a person can only work on one activity at a time.

Activity Selection Problem using Greedy Method | Maximum Disjoint Intervals | DSA-One Course #96

The same video now clearly defines the Activity Selection Problem. This section will help you visualize what we're trying to solve.

Watch from 02:15 to 03:19 to understand the problem statement and the goal: finding the maximum number of non-overlapping intervals.

Finding the Greedy Strategy

How should we greedily pick an activity? Let's consider a few options:

  1. Earliest Start Time: Pick the activity that starts first. This seems intuitive, but what if that activity is very long and prevents many other shorter activities from being scheduled? This strategy can fail.
  2. Shortest Duration: Pick the activity with the smallest duration (finish - start). This also seems plausible, as it occupies our schedule for the least amount of time. However, a short activity might be scheduled at a time that conflicts with two other activities that could have been scheduled back-to-back.
  3. Earliest Finish Time: Pick the activity that finishes first. This is the winning strategy. By choosing an activity that finishes as early as possible, we free up our resource (the person) sooner, thus maximizing the remaining time for other activities.

The following video segment discusses these intuitive but incorrect strategies before arriving at the correct one.

Activity Selection Problem using Greedy Method | Maximum Disjoint Intervals | DSA-One Course #96

Understanding why certain greedy choices fail is as important as knowing the right one. This segment walks through that reasoning process.

Watch from 03:19 to 05:41. The video explains why sorting by duration or start time is flawed and then introduces the correct approach of sorting by finish times.

The Algorithm and Proof of Correctness

The algorithm is straightforward:

  1. Sort the activities based on their finish times in ascending order.
  2. Select the first activity in the sorted list (since it's guaranteed to finish earliest).
  3. Iterate through the remaining activities. For each activity, if its start time is greater than or equal to the finish time of the last selected activity, select it.
Activity Selection Problem Algorithm Example
This image illustrates the greedy algorithm for Activity Selection. Activities are sorted by finish time, and then a single pass selects the maximum set of non-overlapping activities. Here, after selecting activity 1 (finishes at time 2), the next compatible activity is 4 (starts at time 3).

But why does this guarantee an optimal solution? The video provides a brief but powerful proof by exchange argument, which is a common way to validate greedy algorithms.

Activity Selection Problem using Greedy Method | Maximum Disjoint Intervals | DSA-One Course #96

In an interview, you might be asked to justify your greedy choice. This part of the video explains the proof, which is a key concept to understand.

Watch from 05:41 to 07:51. The core idea is that if there is an optimal solution that doesn't include the first-finishing activity, you can always swap its first activity with the actual first-finishing activity to get an equally good (or better) solution.

Java Implementation

Let's look at how to implement this in Java. Since you're aiming for interviews, being comfortable with custom sorting using Comparator is essential.

Activity Selection Problem using Greedy Method | Maximum Disjoint Intervals | DSA-One Course #96

This final segment on Activity Selection walks through a Java implementation. Pay close attention to how a 2D array is used to store activities and then sorted using a lambda expression for the comparator.

Watch from 07:51 to 12:44. This is a practical walkthrough of the code, showing how to handle indices, sort based on a specific criterion (finish time), and iterate to build the final result.

Test your understanding!

Given the following activities with (start, finish) times: (1, 3), (2, 5), (3, 4), (5, 7), (8, 9), (6, 10).

What is the maximum number of activities you can perform? Which activities are they?

Show answer
  1. Sort by finish time:

    • (1, 3)
    • (3, 4)
    • (2, 5)
    • (5, 7)
    • (8, 9)
    • (6, 10)
  2. Select activities:

    • Select (1, 3). The last finish time is 3.
    • Next is (3, 4). Start time 3 >= finish time 3. Select (3, 4). Last finish time is 4.
    • Next is (2, 5). Start time 2 < finish time 4. Reject.
    • Next is (5, 7). Start time 5 >= finish time 4. Select (5, 7). Last finish time is 7.
    • Next is (8, 9). Start time 8 >= finish time 7. Select (8, 9). Last finish time is 9.
    • Next is (6, 10). Start time 6 < finish time 9. Reject.

The maximum number of activities is 4. They are (1, 3), (3, 4), (5, 7), and (8, 9).


3. Fractional Knapsack Problem

Let's move to our second problem. This one highlights a key difference from a similar DP problem you'll encounter.

Problem Statement

You are given n items, each with a value and a weight. You have a knapsack with a maximum weight capacity W. You want to fill the knapsack with items to maximize the total value. The key detail is that you can take fractions of items.

This "fractional" property is what distinguishes it from the 0/1 Knapsack problem (where you must take an item whole or leave it), which requires Dynamic Programming.

L13. Fractional Knapsack Algorithm

The 'take U forward' channel has a great video explaining the Fractional Knapsack problem. This first segment introduces the problem and emphasizes the ability to take fractions.

Watch from 00:03 to 02:50. Notice how you can take a portion of an item if the whole item doesn't fit.

Finding the Greedy Strategy

Again, we need a heuristic.

  1. Highest Value: Pick the most valuable items first? This might fail if the most valuable item is also extremely heavy, filling our knapsack for little "efficiency".
  2. Lightest Weight: Pick the lightest items first? This might fill our knapsack with items that are not very valuable.
  3. Highest Value-to-Weight Ratio: For each item, calculate its "density" (value / weight). Pick items with the highest density first. This is the winning strategy, as it ensures that for every unit of weight we add to our knapsack, we are getting the maximum possible value.

The following segments demonstrate why the other strategies fail and why the value/weight ratio is the correct greedy choice.

L13. Fractional Knapsack Algorithm

This part of the video explores potential greedy strategies, showing why choosing by highest value is not optimal and introducing the value-per-weight ratio logic.

Watch from 04:41 to 08:36. The explanation here is crucial for building the intuition behind the correct greedy approach.

Algorithm and Implementation

The algorithm follows directly from our greedy choice:

  1. For each item, calculate its value-to-weight ratio (v/w).
  2. Sort the items in descending order based on this ratio.
  3. Iterate through the sorted items and add them to the knapsack.
    • If the entire item fits, take it all. Update the total value and remaining capacity.
    • If the entire item does not fit, take a fraction of it that fills the remaining capacity exactly. Add the value of this fraction to the total and stop.

For a detailed step-by-step walkthrough, implementation details, and Java code, the following article is an excellent resource.

Fractional Knapsack

This article from Hope College provides a complete guide to the Fractional Knapsack problem. It includes the algorithm, a detailed example, a Java implementation using a custom Comparable class, and complexity analysis.

Read through this resource. Pay special attention to: The clear high-level algorithm and pseudocode. The worked-out example, which makes the process tangible. The Java implementation, which uses an Item class implementing Comparable. This is a very clean and object-oriented way to structure your solution.

The video from 'take U forward' also provides a solid walkthrough of the implementation logic and how to write the custom comparator, which you can use to supplement your reading.

L13. Fractional Knapsack Algorithm

This segment details the implementation steps, including sorting with a custom comparator and iterating to fill the knapsack. It's a great practical guide.

Watch from 08:36 to 18:09. This complements the text resource by vocalizing the coding process and re-emphasizing the complexity analysis.

Test your understanding!

You have a knapsack with a capacity of W = 50. You are given the following items (value, weight):

  • Item 1: (60, 10)
  • Item 2: (100, 20)
  • Item 3: (120, 30)

What is the maximum value you can carry?

Show answer
  1. Calculate ratios (value/weight):

    • Item 1: 60 / 10 = 6
    • Item 2: 100 / 20 = 5
    • Item 3: 120 / 30 = 4
  2. Sort by ratio (descending):

    • Item 1 (ratio 6)
    • Item 2 (ratio 5)
    • Item 3 (ratio 4)
  3. Fill the knapsack:

    • Take all of Item 1. Value += 60. Remaining capacity = 50 - 10 = 40.
    • Take all of Item 2. Value += 100. Remaining capacity = 40 - 20 = 20.
    • Item 3 has weight 30, but we only have 20 capacity left.
    • Take a fraction of Item 3: 20 / 30 = 2/3.
    • Value from fraction = (2/3) * 120 = 80.
    • Remaining capacity = 20 - 20 = 0.

Total value = 60 + 100 + 80 = 240.


Conclusion

Today you've learned the fundamentals of the greedy algorithmic paradigm and applied it to two classic problems. Unlike DP, which can feel like a brute-force search, greedy algorithms require you to find a clever, provably correct heuristic.

Key Takeaways:

  • Greedy Algorithm: An approach that makes the locally optimal choice at each step, hoping to find a global optimum.
  • When It Works: It works only for problems with the greedy choice property (a local optimum is part of a global optimum) and optimal substructure.
  • Activity Selection: The greedy strategy is to sort activities by their finish times and pick the next compatible one.
  • Fractional Knapsack: The greedy strategy is to sort items by their value-to-weight ratio and take as much as you can of the highest-ratio items.
  • Implementation: The key technical skill in both problems is often custom sorting, which you can do efficiently in Java using Comparators or Comparable interfaces.

Preview of the Next Lesson:
In our next lesson, we will tackle problems involving merging overlapping intervals. This is a very common interview pattern that builds directly on the skills you practiced today, particularly sorting collections of objects based on a specific property to simplify a problem.