【算法 - 动态规划】京东面试题 - 洗咖啡杯问题

2024-02-24 10:52

本文主要是介绍【算法 - 动态规划】京东面试题 - 洗咖啡杯问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

通过前面多篇文章的学习,相信小伙伴们对动态规划的 分类 以及做题 套路 已经有了清晰的认识。包括从左到右模型、范围尝试模型、样本对应模型。

本文我们继续学习一种新的模型 —— 业务限制模型

咖啡杯变干净

给定数组 arr 和整数 N。 arr[i]的长度代表有几个咖啡机,arr[i] 代表第 i 号咖啡机泡一杯咖啡所需要的时间,N 表示有多少个人正在等待着泡咖啡,每台咖啡机只能轮流泡咖啡。喝完咖啡的杯子有两种方式变干净:用 咖啡机洗 或者 自己挥发 干净。

洗咖啡杯机器只有一台,每次 耗时 a 洗干净 一个咖啡杯,洗完才能洗下一个杯子(串行);每个咖啡杯也可以 耗时 b 自己挥发干净 ,咖啡杯可以并行挥发。

假设所有人拿到咖啡后立刻喝完,求出从开始等待到所有咖啡杯都变干净的最短时间

思路分析

本道题先逆向思考一下,因为洗咖啡杯的时间和咖啡杯自己挥发干净所需要的时间都是固定的,因此,要想求出所有咖啡杯变干净的最短时间。就需要让咖啡杯尽可能的早点空出来等待清洗,也就是,让更多的人尽快喝到咖啡。

因此,就可以将本题划分为两道题目。

  • 问题 1: 如何安排每个人在哪个咖啡机前等咖啡(影响到了什么时候能喝到咖啡并等待清洗)。
  • 问题 2: 当杯子等待清洗时如何选择清理方式:机洗 还是 挥发

我们一个一个的破解。

问题 1 思路

影响每个人什么时候喝到咖啡的因素有两个:咖啡机空闲的时间点做一杯咖啡需要的时间(arr[i])

想象一下,如果你来到咖啡机前喝咖啡想要早点结束,你会去哪个咖啡机呢?很简单,当然是去以上 两个因素相加最小的咖啡机前

因此,对于如何安排人到哪台咖啡机前等待,可以使用小根堆进行计算。

代码

public static class Machine {public int timePoint;public int workTime;public Machine(int t, int w) {timePoint = t;workTime = w;}
}public static class MachineComparator implements Comparator<Machine> {@Override// 小根堆// 两者总和小的排在前面public int compare(Machine o1, Machine o2) {return (o1.timePoint + o1.workTime) - (o2.timePoint + o2.workTime);}
}public static int[] minTime(int[] arr, int n, int a, int b) {PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());for (int i = 0; i < arr.length; i++) {heap.add(new Machine(0, arr[i]));}int[] drinks = new int[n];// 计算出每个顾客的最佳喝咖啡时间// 即每个咖啡杯最早空出等待清洗的时间for (int i = 0; i < n; i++) {Machine cur = heap.poll();cur.timePoint += cur.workTime;drinks[i] = cur.timePoint;heap.add(cur);}return drinks;// 调用问题 2 的暴力递归// return bestTime(drinks, a, b, 0, 0);// 调用问题 2 的动态规划// return bestTimeDp(drinks, a, b);
}

代码解释

当每个顾客来到后,弹出小根堆的堆顶,此时就是最佳的喝咖啡方案,即最早喝完咖啡。做完咖啡后,该咖啡机的工作时间更新为当前时间,即 cur.timePoint += cur.workTime ,也就是该顾客喝完咖啡释放杯子的时间,记录在 drinks[i] 数组中。然后将更新后的咖啡机加入小根堆中,继续为后面的顾客服务,直到所有的 N 个人结束。返回 drinks 数组,表示需要清洗的杯子的到来时间。

问题 2 思路

对于问题 2 ,这就是最熟悉的 从左往右的选择模型 ,对于每个咖啡杯来说,有两种可能的选择,要么选择使用咖啡清洗机清洗要么选择挥发干净。我们按照套路先写出递归版本:

递归的准备

注意是 从左到右 的模型哦!

定义递归函数的功能: 返回 drinks 中 [index...] 范围上咖啡杯都变干净的最早结束时间。

思考递归需要的参数: drinks 数组,清洗杯子的时间 a,挥发干净的时间 b,此时来到的下标 index 以及清洗机器什么时候是空闲的可以使用 free。

明确递归的边界条件:index == drinks.length 时说明已经没有杯子要洗了,根据函数功能可知,返回 0 。

思路

从左到右的尝试模型 ,因此,递归就可以按照 当前位置如何选择 的思路来划分情况:

  • 第 index 号杯子选择 使用机器清洗;
  • 第 index 号杯子选择 挥发干净。

因为要求 最短 时间,因此要返回这两种情况当中的最小值。

代码

// free : 清洗咖啡的机器空闲的时间
public static int bestTime(int[] drinks, int a, int b, int index, int free) {if (index == drinks.length) {return 0;}// index 号杯子 选择清洗int selfCleanA = Math.max(drinks[index], free) + a;int restCleanA = bestTime(drinks, a, b, index + 1, selfCleanA);int p1 = Math.max(selfCleanA, restCleanA);// index 号杯子 选择挥发int selfCleanB = drinks[index] + b;int restCleanB = bestTime(drinks, a, b, index + 1, free);int p2 = Math.max(selfCleanB, restCleanB);return Math.min(p1, p2);
}

代码解释

1. 选择机器清洗
  • 当一个杯子选择机器清洗时,drinks[index] 表示自己什么时间可以洗,但此时机器可能还没有空闲。因此,需要选择二者的最大值,也就是既能可以开始洗机器也空闲的时间。洗杯子需要 a 的时间,因此杯子变得干净的时间就等于 Math.max(drinks[index], free) + a
  • 因为要返回的是 index 之后所有的杯子均变干净时间,所以还要考虑剩下的杯子变干净时间。
  • bestTime(drinks, a, b, index + 1, selfCleanA); 继续从下一个杯子开始调用递归函数,此时洗杯子机器空闲的时间正是上一个杯子变干净的时间 selfCleanA
  • 返回当前杯子变干净时间和以后所有杯子变干净时间的最大值,才是所有杯子变干净的最早时间。
2. 选择挥发干净
  • 当一个杯子选择挥发干净(并行)时,不会受到其他因素的影响。因此变干净的时间为 drinks[index] + b
  • 剩下的杯子继续递归调用,此时清洗的机器可用时间不变仍是 free
  • 返回当前杯子变干净时间和以后所有杯子变干净时间的最大值,才是所有杯子变干净的最早时间。

最终返回两种情况中最小的时间,即所有杯子变干净的最早时间。


下面对代码进行优化,改出 动态规划 版本。

思路

首先确定可变参数的范围。

递归函数中,可变的参数有 indexfree 两个。index 的范围很好确定:

    if (index == drinks.length) {return 0;}

由于 index 的值从 0 可以到 drinks.length ,因此设置 dp 表的长度为 drinks.length + 1

麻烦的是 free ,题目中并不能直观的知道清洗机器空闲的最大时间。这就是今天所讲的新模型 —— 业务限制模型不明确的知道一个参数的变化范围,通过业务的限制找到

假设所有的杯子都用机器洗,则能够达到的时间就是 free 能够达到的最大时间,不可能再比这个时间长了!因此,可以先求出这个最大时间,从而设置 dp 数组的大小。

int N = drinks.length;
int maxFree = 0;
for (int i = 0; i < N; i++) {maxFree = Math.max(maxFree, drinks[i]) + a;
}

设置二维 dp 表的大小为 dp[N + 1][maxFree + 1]

代码

public static int bestTimeDp(int[] drinks, int a, int b) {int N = drinks.length;int maxFree = 0;for (int i = 0; i < N; i++) {maxFree = Math.max(maxFree, drinks[i]) + a;}int[][] dp = new int[N + 1][maxFree + 1];// dp[N][...] 都等于 0for (int index = N - 1; index >= 0; index--) {for (int free = 0; free <= maxFree; free++) {int selfCleanA = Math.max(drinks[index], free) + a;// 若超过了最大边界,之后不可能访问到此下标if (selfCleanA > maxFree) {break;}// index 号杯子 选择清洗int restCleanA = dp[index + 1][selfCleanA];int p1 = Math.max(selfCleanA, restCleanA);// index 号杯子 选择挥发int selfCleanB = drinks[index] + b;int restCleanB = dp[index + 1][free];int p2 = Math.max(selfCleanB, restCleanB);dp[index][free] = Math.min(p1, p2);}}return dp[0][0];
}

理解了参数范围的求解方法,修改出动态规划版本的代码就很容易了。

if (index == drinks.length) {return 0;
}

由递归的 base case 可知,dp[N][...] 均为 0 (数组中的值默认为 0 ,无需单独设置了)。index 下标均依赖 index + 1 的值,因此需要倒着遍历数组。

需要注意的是可能越界的情况,这些地方在 dp 表中一定不会被访问到,因此直接跳过。由递归调用含义可知,最终返回 dp[0][0] 即为答案。

总结

本文通过一道面试题目了解了什么是 业务限制模型 。至此,关于如何对递归进行分析的四种基本模型都介绍完了。根据不同模型的套路相信小伙伴也能够轻松应对类似的题目了!再来总结一下:

  1. 从左到右模型arr[index ...]index 之前的不用考虑,只考虑后面的该如何选择
  2. 范围尝试模型 :思考 [L ,R] 两端,即 开头和结尾 处分别该如何取舍。
  3. 样本对应模型 :以 结尾位置 为出发点,思考两个样本的结尾都会产生哪些可能性 。
  4. 业务限制模型 :不能够明确的知道一个参数的变化范围,通过业务的限制找到 最差情况 进行估计。

以上四种模型均能够在往期文章中找到对应的题目哦!

~ 点赞 ~ 关注 ~ 不迷路 ~!!!

------------- 往期回顾 -------------
【算法 - 动态规划】从零开始学动态规划!(总纲)
【算法 - 动态规划】原来写出动态规划如此简单!
【算法 - 动态规划】最长公共子序列问题
【算法 - 动态规划】最长回文子序列
【算法 - 动态规划】力扣 691. 贴纸拼词
【堆 - 专题】“加强堆” 解决 TopK 问题!
AC 此题,链表无敌!!!

这篇关于【算法 - 动态规划】京东面试题 - 洗咖啡杯问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/741888

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出

Python中Tensorflow无法调用GPU问题的解决方法

《Python中Tensorflow无法调用GPU问题的解决方法》文章详解如何解决TensorFlow在Windows无法识别GPU的问题,需降级至2.10版本,安装匹配CUDA11.2和cuDNN... 当用以下代码查看GPU数量时,gpuspython返回的是一个空列表,说明tensorflow没有找到

springboot如何通过http动态操作xxl-job任务

《springboot如何通过http动态操作xxl-job任务》:本文主要介绍springboot如何通过http动态操作xxl-job任务的问题,具有很好的参考价值,希望对大家有所帮助,如有错... 目录springboot通过http动态操作xxl-job任务一、maven依赖二、配置文件三、xxl-

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

IDEA Maven提示:未解析的依赖项的问题及解决

《IDEAMaven提示:未解析的依赖项的问题及解决》:本文主要介绍IDEAMaven提示:未解析的依赖项的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录IDEA Maven提示:未解析的依编程赖项例如总结IDEA Maven提示:未解析的依赖项例如