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.
Constraints:
maxBoxes
boxes or maxWeight
total weight.The task is to compute the minimum number of port visits needed to deliver all the boxes.
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:
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.
We use dynamic programming with an optimized sliding window approach:
dp[i]
be the minimum number of port visits needed to deliver the first i
boxes.
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
).
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).j
indices (where the trip constraints hold) as you move i
forward.
dp[0] = 0
(no boxes, zero visits).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.
Suppose boxes = [[1,1],[2,1],[1,1]]
, maxBoxes = 2
, maxWeight = 3
.
The DP will try all valid windows and find the minimum port visits.
Brute-force: For each position, try all previous splits, resulting in exponential time (O(2^n)).
Optimized DP + Sliding Window:
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.
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];
};