Problem

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 portsCountmaxBoxes, 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.

Examples

Example 1:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
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 3, then returns to storage. 2 trips.
So the total number of trips is 2 + 2 + 2 = 6.

Example 3:

1
2
3
4
5
6
7
8
9
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.

Solution

Method 1 - Using DP

This problem can be solved using a sliding window approach combined with dynamic programming to compute the minimum number of trips efficiently:

  1. Constraints Management:
    • While iterating over the boxes list, track:
      • currentWeight: Total weight of loaded boxes.
      • currentBoxes: Total number of boxes loaded.
    • Ensure that both are within given constraints (maxWeight and maxBoxes).
  2. Port Visits:
    • Use a set or a deque to count distinct ports in the batch since visiting the same port consecutively does not require an additional trip.
  3. Dynamic Programming Array:
    • Maintain a DP array dp[i] where dp[i] represents the minimum trips required to deliver the first i boxes.
    • Base Case: dp[0] = 0, as no trips are needed if there are no boxes.
  4. Transition:
    • For each possible range of boxes to load ([j...i-1]):
      • Count distinct ports in this range.
      • Update dp[i] using the state dp[j]:
        1
        
        dp[i] = min(dp[i], dp[j] + 1 + distinctPortCount)
        

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {

    public int boxDelivering(
        int[][] boxes,
        int portsCount,
        int maxBoxes,
        int maxWeight
    ) {
        int n = boxes.length;
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;

        int curWeight = 0, curBoxes = 0, distinctPorts = 0;
        Deque<Integer> portQueue = new ArrayDeque<>();

        for (int i = 0, j = 0; i < n; i++) {
            // Add current box
            curWeight += boxes[i][1];
            curBoxes++;

            if (portQueue.isEmpty() || portQueue.peekLast() != boxes[i][0]) {
                portQueue.add(boxes[i][0]);
                distinctPorts++;
            }

            // Keep within constraints
            while (curWeight > maxWeight || curBoxes > maxBoxes) {
                curWeight -= boxes[j][1];
                curBoxes--;
                if (portQueue.peekFirst() == boxes[j][0]) {
                    portQueue.pollFirst();
                    if (
                        portQueue.isEmpty() ||
                        portQueue.peekFirst() != boxes[j][0]
                    ) {
                        distinctPorts--;
                    }
                }
                j++;
            }

            // Update dp[i+1]
            dp[i + 1] = dp[j] + distinctPorts + 1; // trips for this segment + return trip
        }

        return dp[n];
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution:
    def boxDelivering(self, boxes: List[List[int]], portsCount: int, maxBoxes: int, maxWeight: int) -> int:
        n = len(boxes)
        dp = [float('inf')] * (n + 1)
        dp[0] = 0

        cur_weight, cur_boxes, distinct_ports = 0, 0, 0
        port_queue = deque()

        j = 0
        for i in range(n):
            # Add current box
            cur_weight += boxes[i][1]
            cur_boxes += 1

            if not port_queue or port_queue[-1] != boxes[i][0]:
                port_queue.append(boxes[i][0])
                distinct_ports += 1

            # Keep within constraints
            while cur_weight > maxWeight or cur_boxes > maxBoxes:
                cur_weight -= boxes[j][1]
                cur_boxes -= 1
                if port_queue and port_queue[0] == boxes[j][0]:
                    port_queue.popleft()
                    if not port_queue or port_queue[0] != boxes[j][0]:
                        distinct_ports -= 1
                j += 1

            # Update dp[i + 1]
            dp[i + 1] = dp[j] + distinct_ports + 1  # trips for current segment + return trip

        return dp[n]

Complexity

  • ⏰ Time complexity: O(n^2) in the worst case, though the sliding window implementation reduces the practical runtime.
  • 🧺 Space complexity:  O(n) for the dp array and sliding window storage.