This blog post is assisted by AI

When preparing for interviews, you'll often come across optimization problems that test both your problem-solving skills and your ability to write efficient code. I have such an interesting programming interview question years ago. The Efficient Shipping problem. Let's break it down and analyze the solution. And to understand the flaws in the first version of my code and how we can improve it. 

Problem Statement: Efficient Shipping


You are tasked with maximizing the number of units that can be shipped using a truck with a limited capacity. Here's how the problem is defined:

"A warehouse manager needs to create a shipment to fill a truck. All of the products in the warehouse are in boxes of the same size. Each product is packed in some number of units per box. Given the number of boxes the truck can hold, determine the maximum number of units of any mix of products that can be shipped."

Example

  • boxes = [1, 2, 3] (1 box of Product 0, 2 boxes of Product 1, 3 boxes of Product 2)
  • unitsPerBox = [3, 2, 1] (Product 0 has 3 units/box, Product 1 has 2 units/box, Product 2 has 1 unit/box)
  • trucksize = 3 (The truck can carry 3 boxes in total)

Output

  • The maximum number of units that can be shipped is 3+2+2 = 7

Issues in the First Version of My Code


At the interview, the code I use was:

public static long GetMaxUnits(List<long> boxes, List<long> unitsPerBox, long trucksize)
{
	long result = 0;
	long cap = 0; 
	long max = 0;

	for (int i = 0; i < boxes.Count; i++)
	{
		cap += boxes[i]; 
		if (cap <= trucksize)
		{
			max += boxes[i] * unitsPerBox[i]; 
			if (result < max) result = max;
		}
	}

	return result;
}

It's one of the solution that you can easily Googled it online, like this https://www.youtube.com/watch?v=GadpHxosSTw

At first glance, the code seems like it's trying to calculate the maximum units by iterating through the list of boxes and adding them to the truck until the capacity is reached. However, there are several issues with this implementation:

  1. No Sorting by Units per Box:

    • The problem is about maximizing the number of units, but the code does not prioritize products with the highest units per box. Instead, it processes the boxes in the order they appear in the input.
    • For example, if unitsPerBox = [1, 3, 2], the truck might pick low-value boxes first, leading to a suboptimal result.
  2. Incorrect Capacity Handling (cap):

    • The variable cap tracks how many boxes have been added so far. However, it doesn't handle cases where adding a product would exceed the truck’s capacity. The code assumes that all boxes of a product can fit into the truck, which is not always true.
  3. Inefficiency:

    • The algorithm has a time complexity of O(n), but it doesn't maximize the units shipped because it doesn't sort or prioritize the boxes effectively.

Improved Solution


To solve this problem correctly, we need to focus on greedy optimization. The key idea is to always pick boxes with the highest unitsPerBox first. Here’s how we can approach it:

  1. Pair Boxes with Units per Box: Create a list of tuples where each tuple represents a product (boxes[i]unitsPerBox[i]).

  2. Sort by Units per Box: Sort the list in descending order of unitsPerBox.

  3. Iterate and Fill the Truck: Start adding boxes to the truck, prioritizing products with the most units per box. If the truck's capacity is exceeded, take only the number of boxes that fit.

This is now the corrected code:

public static long GetMaxUnits(List<long> boxes, List<long> unitsPerBox, long trucksize)
{
    // Pair boxes with unitsPerBox and sort by unitsPerBox in descending order
    var products = boxes
        .Select((box, index) => new { Boxes = box, UnitsPerBox = unitsPerBox[index] })
        .OrderByDescending(product => product.UnitsPerBox)
        .ToList();

    long result = 0;

    foreach (var product in products)
    {
        if (trucksize == 0) break;

        // Take as many boxes as the truck can fit
        long boxesToTake = Math.Min(product.Boxes, trucksize);

        // Add units to the result
        result += boxesToTake * product.UnitsPerBox;

        // Decrease truck capacity
        trucksize -= boxesToTake;
    }

    return result;
}

Step-by-Step Explanation of the Code

Pairing and Sorting

We pair each product's boxes and unitsPerBox into a list of objects. Then, we sort this list in descending order based on unitsPerBox to prioritize the most valuable products.

Iterating Through Products

For each product, we calculate how many boxes can be taken without exceeding the truck’s capacity. Multiply the number of boxes taken by the unitsPerBox to calculate the total units added to the truck.

Updating the Truck Capacity

After adding boxes of a product, we reduce the truck’s remaining capacity (trucksize).

Breaking Early

If the truck is full (trucksize == 0), we stop processing further products.

Complexity Analysis

  • Sorting: Sorting the products by unitsPerBox takes O(n log ⁡n), where n is the number of products.
  • Iteration: Iterating through the sorted products takes O(n).
  • Overall Time Complexity: O(n log⁡ n).

Example Walkthrough


Let’s run the example input:

void Main()
{
	List<long> boxes = [1, 2, 3];
	List<long> unitsPerBox = [3, 2, 1];
	long trucksize = 3;
	
	var result = GetMaxUnits(boxes, unitsPerBox, trucksize);
	Console.WriteLine(result);
}
  1. Pair and sort:

    • Products: [{Boxes = 1, UnitsPerBox = 3}, {Boxes = 2, UnitsPerBox = 2}, {Boxes = 3, UnitsPerBox = 1}].
  2. Start filling the truck:

    • Take 1 box of Product 0 (1×3=3 units).
    • Take 2 boxes of Product 1 (2×2=4 units).
  3. Truck is full. Total units = 3+4=7.

Conclusion


This problem is a great example of how sorting and greedy algorithms can be applied to solve real-world optimization problems. The corrected solution uses a greedy approach to maximize the number of units shipped. By sorting the products based on their value (units per box) and strategically filling the truck, we ensure the most efficient use of the truck’s capacity.