LeetCode1687题
我们想到可以用动态规划来解题,定义
d
p
[
i
]
dp[i]
dp[i]表示前
i
i
i个货物需要的最少运输次数。
初始状态
d
p
[
0
]
=
0
,
d
p
[
i
]
=
∞
(
i
∈
[
1
,
n
]
)
dp[0]=0,dp[i]=\infty(i\in[1,n])
dp[0]=0,dp[i]=∞(i∈[1,n])表示一个货物都没有运输时是0次,其他默认初始化为无穷大。
状态转移方程
d
p
[
i
]
=
m
i
n
{
d
p
[
j
]
+
d
i
f
f
+
2
}
dp[i]=min\{dp[j]+diff+2\}
dp[i]=min{dp[j]+diff+2},其中
j
<
i
j<i
j<i并且区间
[
j
+
1
,
i
]
[j+1,i]
[j+1,i]的货物的个数不超过最大载货个数限制和最大运输重量限制,
d
i
f
f
diff
diff表示区间
[
j
+
1
,
i
]
[j+1,i]
[j+1,i]的相邻的不同货物的个数。
我们有如下代码:
public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
int n = boxes.length;
int[] dp = new int[n + 1];
Arrays.fill(dp, 400000);
dp[0] = 0;
for (int i = 0; i < n; i++) {
int sumWeight = 0, diff = 0, pre = boxes[i][0];
for (int j = i; j >= 0 && j > i - maxBoxes; j--) {
sumWeight += boxes[j][1];
if (sumWeight > maxWeight) {
break;
}
if (boxes[j][0] != pre) {
diff++;
pre = boxes[j][0];
}
dp[i + 1] = Math.min(dp[i + 1], dp[j] + diff + 2);
}
}
return dp[n];
}
以上代码的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2),我们需要进行优化。思考的地方在第二重循环找使得
d
p
[
i
]
dp[i]
dp[i]最小的下标
j
j
j这里。
我们可能需要把上面的状态转移方程重新写一下才能发现可以优化的点。引入前缀和
p
r
e
D
[
i
]
preD[i]
preD[i]表示前
i
i
i个货物中相邻货物不相同的个数,然后我们可以重新写一下状态转移方程。
d
p
[
i
]
=
m
i
n
{
d
p
[
j
]
+
p
r
e
D
[
i
+
1
]
−
p
r
e
D
[
j
]
}
dp[i]=min\{dp[j]+preD[i+1]-preD[j]\}
dp[i]=min{dp[j]+preD[i+1]−preD[j]}
对于当前的
d
p
[
i
]
dp[i]
dp[i]而言,其
p
r
e
D
[
i
+
1
]
preD[i+1]
preD[i+1]是确定的,所以最小的值一定是满足条件的
j
j
j是
d
p
[
j
]
−
p
r
e
D
[
j
]
dp[j]-preD[j]
dp[j]−preD[j]最小的那一个。
所以,我们可以维护一个单调双端队列,如果当前准备放入队列的下标
i
i
i的
d
p
[
i
]
−
p
r
e
D
[
i
]
dp[i]-preD[i]
dp[i]−preD[i]比队列中下标对应的值小,说明队列中的该下标已经不会对更新产生贡献,可以出队列。同时还需要检查队列中的下标对应的位置是否满足最大装货次数和最大重量的限制,如果超出限制,也需要出队列。
每个下标最多进出队列一次,时间复杂度为
O
(
1
)
O(1)
O(1)
最终代码如下
public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
int n = boxes.length;
int[] dp = new int[n + 1];
Arrays.fill(dp, 400000);
dp[0] = 0;
int[] preW = new int[n + 1];
int[] preD = new int[n + 1];
for (int i = 0; i < n; i++) {
preW[i + 1] = preW[i] + boxes[i][1];
if (i > 0) {
preD[i + 1] = preD[i] + (boxes[i][0] == boxes[i - 1][0] ? 0 : 1);
}
}
int[] g = new int[n + 1];
Deque<Integer> q = new ArrayDeque<>();
q.offer(0);
for (int i = 1; i <= n; i++) {
while (i - q.peekFirst() > maxBoxes || preW[i] - preW[q.peekFirst()] > maxWeight) {
q.pollFirst();
}
dp[i] = Math.min(dp[i], g[q.peekFirst()] + preD[i] + 2);
if (i < n) {
g[i] = dp[i] - preD[i + 1];
while (!q.isEmpty() && g[q.peekLast()] > g[i]) {
q.pollLast();
}
q.offerLast(i);
}
}
return dp[n];
}
本题想到动态规划如何定义状态和状态转移,还需要如何使用单调队列优化状态转移的时间复杂度。