动态规划+单调队列优化_LeetCode1687_从仓库到码头运输箱子

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];
}

本题想到动态规划如何定义状态和状态转移,还需要如何使用单调队列优化状态转移的时间复杂度。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值