Leetcode 1687. 从仓库到码头运输箱子 [四种解法] 动态规划 从朴素出发详细剖析优化步骤

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

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

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

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

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

卡车在将所有箱子运输并卸货后,最后必须回到仓库。

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

 

示例 1:

输入:boxes = [[1,1],[2,1],[1,1]], portsCount = 2, maxBoxes = 3, maxWeight = 3
输出:4
解释:最优策略如下:
- 卡车将所有箱子装上车,到达码头 1 ,然后去码头 2 ,然后再回到码头 1 ,最后回到仓库,总共需要 4 趟行程。
所以总行程数为 4 。
注意到第一个和第三个箱子不能同时被卸货,因为箱子需要按顺序处理(也就是第二个箱子需要先被送到码头 2 ,然后才能处理第三个箱子)。

示例 2:

输入:boxes = [[1,2],[3,3],[3,1],[3,1],[2,4]], portsCount = 3, maxBoxes = 3, maxWeight = 6
输出:6
解释:最优策略如下:
- 卡车首先运输第一个箱子,到达码头 1 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第二、第三、第四个箱子,到达码头 3 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第五个箱子,到达码头 3 ,回到仓库,总共 2 趟行程。
总行程数为 2 + 2 + 2 = 6 。

示例 3:

输入:boxes = [[1,4],[1,2],[2,1],[2,1],[3,2],[3,4]], portsCount = 3, maxBoxes = 6, maxWeight = 7
输出:6
解释:最优策略如下:
- 卡车运输第一和第二个箱子,到达码头 1 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第三和第四个箱子,到达码头 2 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第五和第六个箱子,到达码头 3 ,然后回到仓库,总共 2 趟行程。
总行程数为 2 + 2 + 2 = 6 。

示例 4:

输入:boxes = [[2,4],[2,5],[3,1],[3,2],[3,7],[3,1],[4,4],[1,3],[5,2]], portsCount = 5, maxBoxes = 5, maxWeight = 7
输出:14
解释:最优策略如下:
- 卡车运输第一个箱子,到达码头 2 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第二个箱子,到达码头 2 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第三和第四个箱子,到达码头 3 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第五个箱子,到达码头 3 ,然后回到仓库,总共 2 趟行程。
- 卡车运输第六和第七个箱子,到达码头 3 ,然后去码头 4 ,然后回到仓库,总共 3 趟行程。
- 卡车运输第八和第九个箱子,到达码头 1 ,然后去码头 5 ,然后回到仓库,总共 3 趟行程。
总行程数为 2 + 2 + 2 + 2 + 3 + 3 = 14 。

 

提示:

    1 <= boxes.length <= 105
    1 <= portsCount, maxBoxes, maxWeight <= 105
    1 <= ports​​i <= portsCount
    1 <= weightsi <= maxWeight

解法一: 朴素版本

通过题目发现,我们可以很简单的抽象出一个集合状态, d p [ i ] dp[i] dp[i]即运送前i个箱子需要的最小行程次数,那么怎么进行状态计算呢?我们可以枚举最后一次运送的状态,包括[1,2,3,…maxBoxeds]个箱子,那么枚举运送这些箱子能够产生的最小次数即可。
在这里插入图片描述

状态集合:
    dp[i]:运送前i个箱子需要的最少行程次数
状态计算:
    dp[i] = dp[j - 1] + cost[j, i],  (i - maxB + 1 <= j <= i)
    cost[j, i]代表第k~第i个箱子的行程次数
  • 时间复杂度: O ( n 3 ) O(n^3) O(n3)
  • 空间复杂度: O ( n ) O(n) O(n)
class Solution {
    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length;
        int[] dp = new int[n + 5];
        Arrays.fill(dp, 0x3f3f3f3f);
        dp[0] = 0; //初始状态为0
        for (int i = 1; i <= n; i++) {
            int sum = 0;
            for (int j = i; j >= 1 && j >= i - maxBoxes + 1; j--) {
                sum += boxes[j - 1][1]; //累加箱子的种类之和
                if (sum > maxWeight) break; //超过了最大重量
                dp[i] = Math.min(dp[i], dp[j - 1] + cost(boxes, j, i));
            }
        }  
        return dp[n];
    } 
    int cost(int[][] boxes, int l, int r) {
        int ans = 2, port = boxes[l - 1][0]; //初始话为2,因为返回仓库算一次行程
        while (++l <= r) {
            if (boxes[l - 1][0] == port) continue; //只要相同,那么次数不会增加
            ans++;  //码头不相同运输次数增加1
            port = boxes[l - 1][0];
        }
        return ans;
    }
}

解法二:时间优化

我们首先从状态计算的角度去优化:
在这里插入图片描述

d p [ ] dp[] dp[]数组右边的所有式子可以看作在一个窗口内,窗口的大小为maxBoxes,而我们现在要求的是窗口中的最小值,并且随着 i i i的增加窗口会向右移动。那么即转化为求滑动窗口的最小值,使用单调队列求解


但是我们发现两段蓝色部分其实是有些地方不一样的,不一样的地方在于 c o s t cost cost的右端点是不相同的。相比于前一层来说,当前层多了一个 i i i端点。那么如何弥补这个差异呢,我们可以使用 d i f dif dif来表示 c o s t cost cost的差异值,若前一个箱子 i − 1 i-1 i1于当前箱子 i i i的码头相同,那么并不会增加运输次数,那么这次的dif为0,否则就会增加1。由于我们无法直接在队列中进行修改,那么可以考虑增加一个累加值dif,具体看代码实现。


例如:若之前的窗口里面保存的次数为 [ 1 , 2 , 3 ] [1, 2, 3] [1,2,3],那么相对于当前进来值 d p [ i − 1 ] + c o s t [ i , i ] dp[i - 1] + cost[i, i] dp[i1]+cost[i,i]来说要加上以前的差异dif进行比较后,继续构造一个单调递增的队列求解窗口的最小值。最后,将当前的次数 d p [ i − 1 ] + c o s t [ i , i ] − d i f dp[i - 1] + cost[i, i] - dif dp[i1]+cost[i,i]dif放入队列中,减去一个dif是因为队列中保存的是一个相对的运输次数。


同理,我们还要判断重量是否超过了 m a x W e i g h t maxWeight maxWeight, 一样的道理,我们创建一个变量 w e i wei wei来代表重量的偏差值,每次比较时,队列里面的重量要加上偏差值。


那么最后我们的队列里面就存放3个元素值, a , b , c {a,b,c} a,b,c, 其中 a a a为该点的编号用来判断是否在窗口外, b b b为当前值的行程数, c c c为当前的重量之和。


s u r p r i s e surprise surprise,至此可以发现我们不仅优化了第二个循环,顺带将cost函数也进行了优化。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
class Solution {
    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length;
        int[] dp = new int[n + 5];
        Arrays.fill(dp, 0x3f3f3f3f);
        dp[0] = 0;
        Deque<int[]> q = new ArrayDeque<int[]>(); //双端队列
        int dif = 0, wei = 0;
        for (int i = 1; i <= n; i++) {
            int cur = dp[i - 1] + 2;//cur为每次滑动窗口增加的值即dp[i-1]+cost[i,i]
            dif += i >= 2 && boxes[i - 1][0] != boxes[i - 2][0] ? 1 : 0;//dif为运输累加值,由于我们无法直接在队列中进行修改,那么可以考虑增加一个累加值
            wei += boxes[i - 1][1]; //重量要加上当前箱子的重量
            while (!q.isEmpty() && q.peekLast()[1] + dif >= cur) q.pollLast(); //构造一个单调递增的队列
            q.add(new int[]{i, cur - dif, boxes[i - 1][1] - wei}); 
            //判断左端队头是否在窗口外 并且重量不能超过最大重量
            while (q.peekFirst()[0] <= i - maxBoxes || q.peekFirst()[2] + wei > maxWeight) q.pollFirst(); 
            dp[i] = q.peekFirst()[1] + dif; 
        }  
        return dp[n];
    } 
}

解法三:空间优化

利用变量优化 d p dp dp数组

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( k ) , k 为 滑 动 窗 口 大 小 O(k), k为滑动窗口大小 O(k),k
class Solution {
    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length, dp = 0;
        Deque<int[]> q = new ArrayDeque<int[]>();
        int dif = 0, wei = 0;
        for (int i = 1; i <= n; i++) {
            int cur = dp + 2;
            dif += i >= 2 && boxes[i - 1][0] != boxes[i - 2][0] ? 1 : 0;//cost[i, i] = 2
            wei += boxes[i - 1][1];
            while (!q.isEmpty() && q.peekLast()[1] + dif >= cur) q.pollLast();
            q.add(new int[]{i, cur - dif, boxes[i - 1][1] - wei}); 
            while (q.peekFirst()[0] <= i - maxBoxes || q.peekFirst()[2] + wei > maxWeight) q.pollFirst();
            dp = q.peekFirst()[1] + dif; 
        }  
        return dp;
    } 
}

解法四:优先队列

除了使用单调队列求解滑动窗口,那么还可以直接使用单调队列求解其中的最小值。

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)
class Solution {
    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length, dp = 0;
        PriorityQueue<int[]> q = new PriorityQueue<int[]>((a, b)->a[1] - b[1]);
        int dif = 0, wei = 0;
        for (int i = 1; i <= n; i++) {
            int cur = dp + 2;
            dif += i >= 2 && boxes[i - 1][0] != boxes[i - 2][0] ? 1 : 0;//cost[i, i] = 2
            wei += boxes[i - 1][1]; 
            q.add(new int[]{i, cur - dif, boxes[i - 1][1] - wei}); 
            while (q.peek()[0] <= i - maxBoxes || q.peek()[2] + wei > maxWeight) q.poll();
            dp = q.peek()[1] + dif; 
        }  
        return dp;
    } 
}

Note: 另一种思路优化:

我们首先去观察如何优化cost数组,发现可以使用前缀和进行优化。

例如: [ 1 , 2 , 2 , 3 , 4 , 3 , 3 ] [1,2,2,3,4,3,3] [1,2,2,3,4,3,3],这分别是不同码头的箱子,那么怎么快速计算 [ l , r ] [l,r] [l,r]的运输次数。

那么我们可以首先初始化第一个箱子的运输次数 s u m [ 1 ] = 0 sum[1] = 0 sum[1]=0, 若当前箱子与前一个箱子相同,那么次数不会增加 s u m [ i ] = s u m [ i − 1 ] sum[i] = sum[i -1] sum[i]=sum[i1],否则 s u m [ i ] = s u m [ i − 1 ] + 1 。 sum[i] = sum[i - 1] + 1。 sum[i]=sum[i1]+1

最后, s u m = [ 0 , 1 , 1 , 2 , 3 , 4 , 4 ] sum=[0, 1, 1, 2, 3, 4, 4] sum=[0,1,1,2,3,4,4], 那么 c o s t [ l , r ] = s u m [ r ] − s u m [ l ] + 2 cost[l, r] = sum[r] - sum[l] + 2 cost[l,r]=sum[r]sum[l]+2。我们更新我们的状态计算如下:
在这里插入图片描述那么利用前缀和数组计算的话,我们队列里面就只需要存储一下每个点的下标即可,例如 i − m a x B + 1 , . . . , i − 1 i-maxB+1,...,i-1 imaxB+1,...i1,每次通过下标来计算运输次数和重量即可。而解法二是直接优化前缀和数组,通过遍历答案时继续计算


如果有问题,欢迎评论区交流, 如果有帮助到你,请给题解点个赞和收藏哈~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/62307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网页制作课作业基于HTML+CSS+JavaScript+jquery仿慕课网教学培训网站设计实例 企业网站制作

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

【强化学习论文】离线元强化学习中基于对比学习的稳定表示

离线元强化学习中基于对比学习的稳定表示 最近几年来深度强化学习在算法上有很多进展&#xff0c;已初步用在很多场景中。目前深度强化学习有两个重要的问题&#xff1a;数据利用问题&#xff0c;泛化能力。深度强化学习通常要与环境进行大量的交互&#xff0c;通常效率较低&am…

Redis数据库 ---- 五种数据类型常用命令汇总

❤️ 作者简介&#xff1a;大家好我是小鱼干儿♛是一个热爱编程、热爱算法的大三学生&#xff0c;蓝桥杯国赛二等奖获得者&#x1f41f; 个人主页 &#xff1a;https://blog.csdn.net/qq_52007481⭐ 个人社区&#xff1a;【小鱼干爱编程】 文章目录RedisRedis键(key)数据库相关…

Java面向对象:对象的概念及面向对象的三个基本特征

面向对象简称 OO&#xff08;Object Oriented&#xff09;&#xff0c;20 世纪 80 年代以后&#xff0c;有了面向对象分析&#xff08;OOA&#xff09;、 面向对象设计&#xff08;OOD&#xff09;、面向对象程序设计&#xff08;OOP&#xff09;等新的系统开发方式模型的研究。…

element-ui 中 el-tree 和 el-table 样式调整

使用 el-tree 和 el-table 时&#xff0c;往往需要根据项目整体环境做一些样式调整&#xff0c;记录一下常用样式。 el-tree <!-- 树结构 --> <el-treeref"tree":data"data":props"defaultProps":default-expand-all"isExpanded&…

51单片机烟雾报警器mq2烟雾报警ADC0832采集实践制作DIY- GC0026-烟雾报警器

一、功能说明&#xff1a; 基于51单片机设计-烟雾报警器 功能介绍&#xff1a; STC89C52单片机&#xff08;AT89C51/52&#xff09;lcd1602adc0832mq2烟雾传感器蜂鸣器2个按键设定报警阈值 1.通过ADC0832采集MQ2烟雾输出的电压换算位烟雾浓度0~100 2.如果烟雾浓度超过设定…

机器学习9衡量线性回归法的指标,MSE,RMS,MAE

文章目录一、衡量线性回归法的指标&#xff0c;MSE,RMS,MAE1、MSE均方误差&#xff08;Mean Squared Error&#xff09;2、RSE均方误差&#xff08;Root Mean Squared Error&#xff09;3、平均绝对误差MAE&#xff08;Mean Absolute Error&#xff09;二、演示&#xff1a;三、…

清华、北大、中科大、UMA、MSU五位博士生畅聊深度学习理论

点击蓝字关注我们AI TIME欢迎每一位AI爱好者的加入&#xff01;伴随着深度学习的蓬勃发展&#xff0c;进入人们视线的好像都是算法或AlphaGo等应用层面的东西。但是在理论上&#xff0c;深度学习似乎却没有很出圈的相关理论。因此&#xff0c;部分人也在批评深度学习是缺乏理论…

易基因课程回顾|表观遗传学和表观育种在品种改良中的应用研究

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 传统作物育种包括杂交、选择所需性状的遗传变异&#xff0c;导致遗传基础缩窄和遗传多样性缺失&#xff0c;从而阻碍作物改良。表型性状受遗传学和表观遗传学影响&#xff0c;利用表观遗传…

简单个人网页设计作业 静态HTML个人主题网页作业 DW个人网站模板下载 大学生简单个人网页作品代码 个人网页制作 学生个人网页Dreamweaver设计作业

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

Spring框架(十二):实现日志功能通过SpringBean后处理器

实现日志功能通过SpringBean后处理器引子需求分析实现Log功能Spring Bean的后置处理器引子 痛定思痛&#xff0c;主要问题出现在自己雀氏不熟悉框架底层、一些面试题&#xff0c;以及sql的一些情况淡忘了。 本章节的开始是对于过去的重新回顾&#xff0c;当然&#xff0c;我也…

技术分享 | 使用 Zabbix + Grafana 搭建服务器监控系统

搭建 Linux 服务器监控的目的是防止以下现象&#xff1a;自己有一台阿里云服务器内存是 2g 的 , 多开一些软件就会把内存和 CPU 使用率弄的很高&#xff0c;最终导致服务器卡死。 所以基于这个痛点&#xff0c;想知道当前的 CPU 和内存是多少。阿里云 ECS 控制台中也提供对服务…

【自适应滤波】基于FxLMS的样条自适应滤波算法分析(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

DockerCompose安装、使用及微服务部署实操

1 什么是DockerCompose DockerCompose是基于Compose文件帮助我们快速的部署分布式应用。 解决容器需手动一个个创建和运行的问题&#xff01; Compose文件本质上是一个文本文件&#xff0c;其通过指令定义集群中的每个容器如何运行。 我们可以将其看做是将多个docker run命令…

RTLinux的介绍

RTLinux RTLinux是由美国新墨西哥州的fsmlabs(finite state machine labs, 有限状态机实验室)公司开发的、利用linux开发的面向实时和嵌入式应用的操作系统。在rtlinux宣言中&#xff0c;这样描述rtlinux &#xff1a; rtlinux is the hard realtime variant of linux that mak…

基于LLVM的AFL分析

简介 Fuzzing是指通过构造测试输入&#xff0c;对软件进行大量测试来发现软件中的漏洞的一种模糊测试方法。当前大多数远程代码执行和特权提升等比较严重的漏洞都是使用Fuzzing技术挖掘的&#xff0c;Fuzzing技术被证明是当前鉴别软件安全问题方面最强大测试技术。 然而Fuzzin…

Linux Shell 脚本的10个高频面试问答

Linux 的浩瀚无垠&#xff0c;使人总能每次都提交与众不同的内容。这些内容不仅对他们的职业生涯很有用&#xff0c;同时也让他们增长知识。在此&#xff0c;我们就尝试这么去做&#xff0c;至于能取得多大的成功&#xff0c;就由我们的读者朋友们来判断吧。 在此&#xff0c;…

CopyOnWriteArrayList真的线程安全吗?

前几天刷博客时&#xff0c;无意中看到一篇名为《CopyOnWriteArrayList真的完全线程安全吗》博客。心中不禁泛起疑问&#xff0c;它就是线程安全的啊&#xff0c;难道还有啥特殊情况&#xff1f; 我们知道CopyOnWrite的核心思想正如其名&#xff1a;写时复制。在对数据有修改操…

只要背着电脑,他可以去任何地方

12月是微软全球开发者月&#xff0c;MSDN 微软开发者社区将在此期间推出特别专栏《技术狂旅》&#xff0c;解读这些技术狂热爱好者的个人经历&#xff0c;循着他们的人生旅程看到我们自己的影子&#xff0c;希望能带给你一些启发或激励&#xff0c;一起探寻自身更多的可能性。 …

Android三种数据存储的方式

文章目录Android数据存储技术持久化技术文件存储将数据存储到文件当中示例_将数据存储到文件当中示例_从文件当中读取数据SharedPreferences存储将数据存储到SharedPreferences1.Context类中getSharedPreferences()方法2.Activity类中的getSharedPreferences()方法往SharedPref…