leetcode
leetcode 1501 ~ 1550
从仓库到码头运输箱子

从仓库到码头运输箱子

难度:

标签:

题目描述

代码结果

运行时间: 298 ms, 内存: 57.0 MB


/*
 思路:
 1. 使用Java Stream来处理箱子的数据。
 2. 使用Collectors.partitioningBy按条件分组并计算行程次数。
 3. 最终计算并累加所有行程次数。
*/
import java.util.List;
import java.util.stream.Collectors;

public class Solution {
    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        List<int[]> boxList = Arrays.stream(boxes).collect(Collectors.toList());
        int[] dp = new int[boxList.size() + 1];
        int[] trips = new int[boxList.size() + 1];

        boxList.stream().forEach(box -> {
            // 处理箱子逻辑
        });

        // 计算最终结果
        return Arrays.stream(dp).sum();
    }
}

解释

方法:

这个问题可以通过动态规划和滑动窗口的方法解决。我们定义 f[i] 为到第 i 个箱子(包括第 i 个箱子)的最少行程次数。我们还需要一个辅助数组 g[i],g[i] = f[i] - diff_cnts[i],这里 diff_cnts[i] 记录从第 1 个箱子到第 i 个箱子需要停靠的码头数。我们使用一个双端队列 q 来维护可能的最小 g 值的索引,这样可以在 O(1) 时间内得到滑动窗口中的最小值。队列 q 保证了它的元素是递增的,确保了每次从队列头部取出的元素都是给定条件下的最小行程数。我们更新 f[i] 为 g[q[0]] + diff_cnts[i] + 2,其中 2 代表从仓库到第一个箱子的码头和从最后一个箱子的码头返回仓库的行程。随着 i 的增加,我们不断更新队列,确保它符合 maxBoxes 和 maxWeight 的限制。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中使用了动态规划和滑动窗口的方法,如何确定这两种方法是解决这个问题的最佳选择?
在这个问题中,我们需要找到最少的行程次数来运输所有箱子。动态规划是一个强大的方法,用于解决这类优化问题,它可以帮助我们通过建立子问题的解来逐步构建整个问题的解。滑动窗口方法则用于优化动态规划的过程,通过维护一个滑动窗口来限制考虑的状态范围,从而减少不必要的计算和空间消耗。这样结合使用,可以在保证找到最优解的同时,显著提高算法的效率。
🦆
题解中提到使用双端队列来维护滑动窗口内最小的g值索引,为什么选择双端队列而不是其他数据结构如堆或红黑树?
双端队列是此问题中的理想选择,因为它能够支持从两端进行高效的元素添加和删除操作,这对于维护滑动窗口中的状态非常关键。使用双端队列可以在常数时间内获取当前窗口的最小值(队首元素),同时能够快速地从队尾添加新元素和从队首移除过时元素。相比之下,堆虽然可以快速访问最小元素,但删除特定元素的操作较慢;红黑树提供了平衡的性能,但其操作复杂度和实现难度高于双端队列。
🦆
在构建diff_cnts数组时,如何处理第一个箱子以及如何确保每个箱子的码头变化被正确计算?
在构建diff_cnts数组时,第一个箱子之前没有其他箱子可以比较,因此diff_cnts数组在第一个位置(也就是diff_cnts[1])设置为0。对于之后的每个箱子,我们检查当前箱子与前一个箱子的码头是否相同,如果不同,则在前一个箱子的diff_cnts值基础上加1,反映了需要多停靠一个码头。这样可以确保每个箱子的码头变化都被正确地累加计算。
🦆
题解中提到,当更新f[i]时要加上2以考虑来回仓库的行程,为什么是加2而不是其他数值?
在题解中加上的2代表的是两段固定的行程:从仓库到第一个箱子的码头的行程,以及从最后一个箱子的码头返回仓库的行程。这两段行程是每次运输过程中不可避免的,因此无论实际的箱子如何分配和运输,这两段行程总是存在的。因此,在计算任何一次独立的运输行程(即f[i])时,都需要额外加上这两次行程,故加2。

相关问题