{x}
blog image

Delivering Boxes from Storage to Ports

You have the task of delivering some boxes from storage to their ports using only one ship. However, this ship has a limit on the number of boxes and the total weight that it can carry.

You are given an array boxes, where boxes[i] = [ports​​i​, weighti], and three integers portsCount, maxBoxes, and maxWeight.

  • ports​​i is the port where you need to deliver the ith box and weightsi is the weight of the ith box.
  • portsCount is the number of ports.
  • maxBoxes and maxWeight are the respective box and weight limits of the ship.

The boxes need to be delivered in the order they are given. The ship will follow these steps:

  • The ship will take some number of boxes from the boxes queue, not violating the maxBoxes and maxWeight constraints.
  • For each loaded box in order, the ship will make a trip to the port the box needs to be delivered to and deliver it. If the ship is already at the correct port, no trip is needed, and the box can immediately be delivered.
  • The ship then makes a return trip to storage to take more boxes from the queue.

The ship must end at storage after all the boxes have been delivered.

Return the minimum number of trips the ship needs to make to deliver all boxes to their respective ports.

 

Example 1:

Input: boxes = [[1,1],[2,1],[1,1]], portsCount = 2, maxBoxes = 3, maxWeight = 3
Output: 4
Explanation: The optimal strategy is as follows: 
- The ship takes all the boxes in the queue, goes to port 1, then port 2, then port 1 again, then returns to storage. 4 trips.
So the total number of trips is 4.
Note that the first and third boxes cannot be delivered together because the boxes need to be delivered in order (i.e. the second box needs to be delivered at port 2 before the third box).

Example 2:

Input: boxes = [[1,2],[3,3],[3,1],[3,1],[2,4]], portsCount = 3, maxBoxes = 3, maxWeight = 6
Output: 6
Explanation: The optimal strategy is as follows: 
- The ship takes the first box, goes to port 1, then returns to storage. 2 trips.
- The ship takes the second, third and fourth boxes, goes to port 3, then returns to storage. 2 trips.
- The ship takes the fifth box, goes to port 2, then returns to storage. 2 trips.
So the total number of trips is 2 + 2 + 2 = 6.

Example 3:

Input: boxes = [[1,4],[1,2],[2,1],[2,1],[3,2],[3,4]], portsCount = 3, maxBoxes = 6, maxWeight = 7
Output: 6
Explanation: The optimal strategy is as follows:
- The ship takes the first and second boxes, goes to port 1, then returns to storage. 2 trips.
- The ship takes the third and fourth boxes, goes to port 2, then returns to storage. 2 trips.
- The ship takes the fifth and sixth boxes, goes to port 3, then returns to storage. 2 trips.
So the total number of trips is 2 + 2 + 2 = 6.

 

Constraints:

  • 1 <= boxes.length <= 105
  • 1 <= portsCount, maxBoxes, maxWeight <= 105
  • 1 <= ports​​i <= portsCount
  • 1 <= weightsi <= maxWeight

Solution: Dynamic Programming with Monotonic Queue Optimization

This problem can be solved efficiently using dynamic programming, enhanced with a monotonic queue for optimization.

Understanding the Problem

We need to deliver boxes from storage to ports, respecting constraints on the number of boxes and total weight the ship can carry. Boxes must be delivered in order. The goal is to minimize the total number of trips the ship makes. Each trip consists of loading boxes, delivering them (potentially multiple port stops within a single trip), and returning to storage.

Approach

  1. Dynamic Programming: We define dp[i] as the minimum number of trips needed to deliver the first i boxes. The state transition involves considering the last trip: we iterate through possible ending points j for the last trip (from i - maxBoxes to i - 1), and calculate the cost of that trip.

  2. Calculating Trip Cost: A single trip's cost depends on:

    • The number of boxes delivered in that trip (i - j).
    • The number of port changes in that trip. If consecutive boxes go to different ports, that's an extra trip. We use a prefix sum cs to count port changes efficiently.
  3. Monotonic Queue Optimization: The dynamic programming recurrence requires checking all possible j values in a window, leading to O(n^2) complexity. The monotonic queue optimizes this. It stores indices j that minimize dp[j] - cs[j]. This allows us to find the optimal j in O(1) time for each i. The queue is monotonic because we only need to maintain the minimum value; elements with larger values won't improve the solution.

Time Complexity Analysis:

  • The dynamic programming loop iterates n times.
  • Inside the loop, the monotonic queue operations (adding and removing elements) take O(1) amortized time each.
  • The overall time complexity is O(n).

Space Complexity Analysis:

  • We use O(n) space for the dp array, ws (weight prefix sum), cs (port change prefix sum), and the monotonic queue.
  • The space complexity is O(n).

Code Implementation (Python):

from itertools import accumulate, pairwise
from collections import deque
 
class Solution:
    def boxDelivering(self, boxes, portsCount, maxBoxes, maxWeight):
        n = len(boxes)
        ws = list(accumulate(box[1] for box in boxes))  # Prefix sum of weights
        c = [int(a != b) for a, b in pairwise(box[0] for box in boxes)] # Port change indicator
        cs = list(accumulate(c))  # Prefix sum of port changes
        dp = [0] * (n + 1)
        q = deque([0])
        for i in range(1, n + 1):
            while q and (i - q[0] > maxBoxes or ws[i] - ws[q[0]] > maxWeight):
                q.popleft()
            if q:
                dp[i] = dp[q[0]] + cs[i-1] - cs[q[0]] + 2  # Update dp[i] using the best j from the queue
            if i < n:
                while q and dp[q[-1]] - cs[q[-1]] >= dp[i] - cs[i]:
                    q.pop()
                q.append(i) # Add i to the queue if it can improve the solution
        return dp[n]
 

Other Languages:

The solution can be adapted to other languages like Java, C++, and Go using similar data structures and logic. The core algorithm remains the same. The code structure might vary slightly depending on the language's built-in features (e.g., deque implementations). For brevity, I am not including the other languages here, but the adaptation would be straightforward.