leetcode 1687. 从仓库到码头运输箱子 动态规划+单调队列

题目

你有一辆货运卡车,你需要用这一辆车把一些箱子从仓库运送到码头。这辆卡车每次运输有 箱子数目的限制总重量的限制 。

给你一个箱子数组 boxes 和三个整数 portsCount, maxBoxes 和 maxWeight ,其中 boxes[i] = [ports​​i​, weighti] 。

  • ports​​i 表示第 i 个箱子需要送达的码头, weightsi 是第 i 个箱子的重量。
  • portsCount 是码头的数目。
  • maxBoxes 和 maxWeight 分别是卡车每趟运输箱子数目和重量的限制。

箱子需要按照数组顺序运输,同时每次运输需要遵循以下步骤:

卡车从 boxes 队列中按顺序取出若干个箱子,但不能违反 maxBoxesmaxWeight 限制。
对于在卡车上的箱子,我们需要 按顺序 处理它们,卡车会通过 一趟行程 将最前面的箱子送到目的地码头并卸货。如果卡车已经在对应的码头,那么不需要 额外行程 ,箱子也会立马被卸货。
卡车上所有箱子都被卸货后,卡车需要 一趟行程 回到仓库,从箱子队列里再取出一些箱子。
卡车在将所有箱子运输并卸货后,最后必须回到仓库。

请你返回将所有箱子送到相应码头的 最少行程 次数。

题目示例

输入: boxes = [[1,1],[2,1],[1,1]], portsCount = 2, maxBoxes = 3, maxWeight = 3
输出: 4
解释: 最优策略如下:

  • 卡车将所有箱子装上车,到达码头 1 ,然后去码头 2 ,然后再回到码头 1 ,最后回到仓库,总共需要 4 趟行程。
    所以总行程数为 4 。
    注意到第一个和第三个箱子不能同时被卸货,因为箱子需要按顺序处理(也就是第二个箱子需要先被送到码头 2 ,然后才能处理第三个箱子)。

解题思路

动态规划 + 单调队列求滑窗最值问题
由题意,在一次出车中,该次出车的行程多少,取决于车中所装箱子中有多少组相邻箱子的目的地不同,比如,车中所有箱子的目的地均相同,那么仅需2趟行程;若车中有x组相邻的箱子目的地不同,那么该次出车需x + 2趟行程;

  • 为了快速得到某次出车所需要的行程,我们定义一个数组dif,dif[i]表示从box[0]到box[i]中,相邻箱子且目的地不同的组数;于是当某次出车的箱子区间为[x,y]时,该次出车的所需行程则可由dif[y]-dif[x] + 2快速得到;
  • 因为每次出车均有重量限制,为了快速得到某次出车的箱子总重,仍需一个表示重量的前缀和数组w,那么箱子区间为[x+1,y]时(共x - y个),总重为w[y] - w[x];

我么定义dp[i]为将前i只箱子运送完毕所需要的最少行程,为了向前转移,我们假设之前的出车共运送了j只箱子(0~j-1),若能完全得到dp[i]与dp[j]的数学关系,便能得到转移方程;考虑如下几点:

  • 易得该次出车的箱子区间为[j+1,i],于是在满足容量和重量的前提下,该次出车的行程为dif[i] - dif[j+1] + 2,总重为w[i] - w[j],总数为i - j;
  • 该次出车装箱,不能超重量,即w[i] - w[j] 不能大于maxWeight;
  • 该次出车装箱,不能超容量,即i - j不能大于maxBoxes,也就是说j不能小于i - maxBoxes,不能大于i-1;

于是,在满足容量和重量的前提下,对于在[i-maxBoxes,i-1]中的每一个j,有转移方程:

  • dp[i] = min(dp[j] + dif[i] - dif[j+1] + 2;

由于[i-maxBoxes,i-1]是一个滑动区间,填表时,如果对于每一个i,都要枚举每一个区间内的j来求dp[j] -dif[j]的最小值,那么每一次找寻j都需要O(n)的时间,找寻所有滑动区间的总时间复杂度会达到O(n2),按本题所给的数据量105,会超时。

通过某种数据结构,优化滑动区间j的查找(某种数据结构?——> 优先队列 ——> 单调双端队列):

  • 一般地,降低时间复杂度的常用方法,就是避免重复访问,将访问过的数据存储到数据结构中以便之后直接取用(而不用重复访问),即用空间换取时间。由于滑动区间每次仅仅滑动1,如果要按上述找寻j的方法,每次滑窗后都枚举区间中的每一个值,很显然会造成线性级的重复访问,因此我们在填表时,每填到一个i,就将其存储到数据结构中,以此来避免重复访问;
  • 那么选取哪种数据结构呢?我们的需求是,希望能从数据结构中快速找到使dp[j] -dif[j]最小的j,另外,在找到它之后,若这个最优的j不能满足滑窗后容量和重量的要求,那么将它弹出数据结构,否则,将它与滑窗后的新区间的最后一个值i比较,将较优者排在数据结构的最前端;因此,自然就想到了优先队列;
  • 在优先队列的基础上,能否继续优化?我们试想这样一种情况:滑窗过后,i增加了1,之前的i变成了i-1,进入到了滑动区间中,这个区间中的最后一个i,是最能满足题目的容量和重量需求的,因为它离滑窗后的i是最近的,换言之,它是最难因为容量和重量限制而被弹出队列的,在它入列之前,队列中如果存在值x,使得dp[x] - dif[x]比dp[i] - dif[i]大,而如果i不能满足之后的最优需求,那么x更加不会满足,于是将这样的x统统出列,直到遇到一个比i更优的值,再将i入列,以便之后的滑动区间使用(当它之前的值在滑窗后因不满足容量和重量条件弹出队列时)。这种情况下,需要优先队列拓展出【i入列前弹出比i更差的队尾元素】的功能,因此用单调双端队列代替优先队列。

代码

class Solution {
public:
    int boxDelivering(vector<vector<int>>& boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n=boxes.size();
        vector<int> dif(n+1),dp(n+1);//dif[i]表示从0~i有多少个相邻箱子是不同目的地的个数,dp[i]表示运前i个箱子的最小行程数
        vector<long long> w(n+1);//总重的前缀和数组
        deque<int> q{0};
        for(int i=0;i<=n;i++)
        {
            if(i>1)dif[i]=dif[i-1]+(boxes[i-2][0]==boxes[i-1][0]?0:1);
            if(i!=n)w[i+1]=w[i]+boxes[i][1];
        }
        for(int i=1;i<=n;i++)
        {
            while(i-q.front()>maxBoxes||w[i]-w[q.front()]>maxWeight) //若队首元素不满足容量或重量限制,弹出
            {
                q.pop_front();
            }
            //队首永远是最优的j。从队首到队尾,j 的值单调递增,且其dp[j]-dif[j+1]也递增,取用队首后一个值只会让dp[i]变得更大。
            dp[i]=dp[q.front()]+dif[i]-dif[q.front()+1]+2;
            if(i!=n)
            {
                while(!q.empty()&&dp[i]-dif[i+1]<dp[q.back()]-dif[q.back()+1])//i入列前,弹出大于等于i的队尾元素
                {
                    q.pop_back();
                }
                q.push_back(i);
            }
        }
        return dp[n];
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值