Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1687. Delivering Boxes from Storage to Ports - Leetcode Solution

Problem Description

You are given a list of boxes, where each box is represented as [port, weight]. The boxes are stored in a warehouse and must be delivered to their respective ports in order. You have a ship that can carry at most maxBoxes boxes and a total weight of at most maxWeight per trip.

The ship starts at storage (port 0), and must deliver boxes to their destination ports in order. For each trip, the ship can pick up a contiguous sequence of boxes (starting from where it left off), deliver them in order, and return to storage.

  • Each trip's cost is the number of port visits (including the return to storage).
  • The goal is to deliver all boxes in order, minimizing the total number of port visits.

Constraints:

  • All boxes must be delivered in order.
  • You cannot deliver boxes out of order or skip any box.
  • Each trip must not exceed maxBoxes boxes or maxWeight total weight.

The task is to compute the minimum number of port visits needed to deliver all the boxes.

Thought Process

At first glance, the problem looks similar to classical bin packing or partitioning, but the constraints (delivering in order, with both box and weight limits) make brute force infeasible. The naive approach would be to try all possible ways to split the boxes into valid trips, but with n boxes, the number of possible partitions is exponential.

To optimize, we notice:

  • Each trip must take a contiguous segment of boxes.
  • We want to minimize the number of port visits (not trips), so merging boxes with the same destination port in a trip is beneficial.
  • Dynamic programming is a natural fit: for each prefix of boxes, we can record the minimum port visits needed to deliver up to that box.
  • We need an efficient way to check, for any segment, if it fits the maxBoxes and maxWeight constraints, and to compute the number of port visits for that segment.

The main challenge is efficiently iterating all valid previous trip endpoints for each position, and counting port visits without redundant recomputation.

Solution Approach

We use dynamic programming with an optimized sliding window approach:

  1. State Definition: Let dp[i] be the minimum number of port visits needed to deliver the first i boxes.
  2. Transition: For each i, we look for the earliest possible j (where j < i) such that boxes j to i-1 can be delivered in one trip (respecting maxBoxes and maxWeight).
    • For each candidate j, the cost is dp[j] + cost(j, i-1), where cost(j, i-1) is the number of port visits for delivering boxes j to i-1 in one trip (including the return to storage).
  3. Port Visits Calculation: For a trip, count the number of times the port changes as you move through the boxes, plus 1 for the return to storage.
  4. Optimization: Use a queue to maintain the window of valid j indices (where the trip constraints hold) as you move i forward.
    • Maintain cumulative weight and box count.
    • Use a counter to track port changes within the window.
  5. Initialization: dp[0] = 0 (no boxes, zero visits).
  6. Final Answer: dp[n] is the minimum port visits to deliver all boxes.

This approach ensures that each box is considered only a constant number of times, resulting in an efficient solution.

Example Walkthrough

Suppose boxes = [[1,1],[2,1],[1,1]], maxBoxes = 2, maxWeight = 3.

  1. First trip (boxes 0 and 1): Ports visited: 1 (for box 0), then 2 (for box 1), then return to storage. Total: 3 port visits.
  2. Second trip (box 2): Port 1, return to storage. Total: 2 port visits.
  3. Total port visits: 3 (first trip) + 2 (second trip) = 5.
  4. Is there a better way? If we deliver boxes 0 and 2 together, we'd violate the order constraint (box 1 must be delivered before box 2). So, the above is optimal.

The DP will try all valid windows and find the minimum port visits.

Time and Space Complexity

Brute-force: For each position, try all previous splits, resulting in exponential time (O(2^n)).

Optimized DP + Sliding Window:

  • Each box is added and removed from the window at most once: O(n).
  • For each box, we may update counters and DP arrays in O(1) amortized time.
  • Total time complexity: O(n).
  • Space complexity: O(n) for the DP array and auxiliary data structures.

Summary

The problem is efficiently solved using dynamic programming with a sliding window, leveraging the fact that each trip must be a contiguous segment. By carefully tracking the window's constraints and port changes, we can compute the minimum port visits in linear time. The key insight is modeling the problem as DP over prefixes and using a queue to efficiently manage feasible trip windows.

Code Implementation

from collections import deque

class Solution:
    def boxDelivering(self, boxes, maxBoxes, maxWeight, portsCount):
        n = len(boxes)
        dp = [0] + [float('inf')] * n
        # port changes prefix sum
        port_changes = [0] * (n + 1)
        for i in range(1, n):
            port_changes[i] = port_changes[i-1] + (boxes[i][0] != boxes[i-1][0])
        weight = 0
        left = 0
        q = deque()
        q.append(0)
        for right in range(1, n + 1):
            weight += boxes[right-1][1]
            # maintain window constraints
            while (right - left > maxBoxes or weight > maxWeight):
                weight -= boxes[left][1]
                left += 1
            # dp transition
            while q and q[0] < left:
                q.popleft()
            if q:
                trips = port_changes[right-1] - port_changes[q[0]] + 2
                dp[right] = min(dp[right], dp[q[0]] + trips)
            # maintain monotonicity
            while q and dp[q[-1]] - port_changes[q[-1]] >= dp[right] - port_changes[right]:
                q.pop()
            q.append(right)
        return dp[n]
      
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;

class Solution {
public:
    int boxDelivering(vector<vector<int>>& boxes, int maxBoxes, int maxWeight, int portsCount) {
        int n = boxes.size();
        vector<int> dp(n+1, INT_MAX);
        dp[0] = 0;
        vector<int> port_changes(n+1, 0);
        for (int i = 1; i < n; ++i) {
            port_changes[i] = port_changes[i-1] + (boxes[i][0] != boxes[i-1][0]);
        }
        int weight = 0, left = 0;
        deque<int> q;
        q.push_back(0);
        for (int right = 1; right <= n; ++right) {
            weight += boxes[right-1][1];
            while ((right - left > maxBoxes) || (weight > maxWeight)) {
                weight -= boxes[left][1];
                left++;
            }
            while (!q.empty() && q.front() < left) q.pop_front();
            if (!q.empty()) {
                int trips = port_changes[right-1] - port_changes[q.front()] + 2;
                dp[right] = min(dp[right], dp[q.front()] + trips);
            }
            while (!q.empty() && dp[q.back()] - port_changes[q.back()] >= dp[right] - port_changes[right])
                q.pop_back();
            q.push_back(right);
        }
        return dp[n];
    }
};
      
import java.util.*;

class Solution {
    public int boxDelivering(int[][] boxes, int maxBoxes, int maxWeight, int portsCount) {
        int n = boxes.length;
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        int[] portChanges = new int[n + 1];
        for (int i = 1; i < n; i++) {
            portChanges[i] = portChanges[i - 1] + (boxes[i][0] != boxes[i - 1][0] ? 1 : 0);
        }
        int weight = 0, left = 0;
        Deque<Integer> q = new ArrayDeque<>();
        q.add(0);
        for (int right = 1; right <= n; right++) {
            weight += boxes[right - 1][1];
            while (right - left > maxBoxes || weight > maxWeight) {
                weight -= boxes[left][1];
                left++;
            }
            while (!q.isEmpty() && q.peekFirst() < left) q.pollFirst();
            if (!q.isEmpty()) {
                int trips = portChanges[right - 1] - portChanges[q.peekFirst()] + 2;
                dp[right] = Math.min(dp[right], dp[q.peekFirst()] + trips);
            }
            while (!q.isEmpty() && dp[q.peekLast()] - portChanges[q.peekLast()] >= dp[right] - portChanges[right])
                q.pollLast();
            q.add(right);
        }
        return dp[n];
    }
}
      
var boxDelivering = function(boxes, maxBoxes, maxWeight, portsCount) {
    const n = boxes.length;
    const dp = new Array(n + 1).fill(Infinity);
    dp[0] = 0;
    const portChanges = new Array(n + 1).fill(0);
    for (let i = 1; i < n; ++i) {
        portChanges[i] = portChanges[i - 1] + (boxes[i][0] !== boxes[i - 1][0] ? 1 : 0);
    }
    let weight = 0, left = 0;
    const q = [];
    q.push(0);
    for (let right = 1; right <= n; ++right) {
        weight += boxes[right - 1][1];
        while (right - left > maxBoxes || weight > maxWeight) {
            weight -= boxes[left][1];
            left++;
        }
        while (q.length && q[0] < left) q.shift();
        if (q.length) {
            let trips = portChanges[right - 1] - portChanges[q[0]] + 2;
            dp[right] = Math.min(dp[right], dp[q[0]] + trips);
        }
        while (q.length && dp[q[q.length - 1]] - portChanges[q[q.length - 1]] >= dp[right] - portChanges[right])
            q.pop();
        q.push(right);
    }
    return dp[n];
};