1. 问题描述:
为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。
在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。
我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。
示例 1:
输入:time = [1,2,3,3], m = 2
输出:3
解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。
示例 2:
输入:time = [999,999,999], m = 4
输出:0
解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。
限制:
1 <= time.length <= 10^5
1 <= time[i] <= 10000
1 <= m <= 1000
2. 思路分析:
① 仔细分析这道题目可以知道我们需要在一个范围之内查找最终的答案,所以可以知道这个题目其实是一个二分查找的模型,(感觉分析题目为二分查找的模型其实也是一个难点),首先需要制定二分查找的策略,可以知道我们检查当前花费t时间是否可以在m天完成之内即可,对于大部分题目都是类似的,二分查找的基本上都是根据我们求解的答案来查找的,这道题目的答案其实是最终花费的最少的时间,所以我们二分查找的范围就是时间,由题目可以知道最小的范围为0,最大的范围为time数组的元素之和,确定好了范围之后我们就可以在这个范围内查找,当判断出花费时间为mid的可以在m天之内可以完成那么我们就要缩小右边界因为可能时间会更小,不能在m天之内完成那么就需要扩大左边界
② 检查是否能够在m天之内完成我们可以根据题目的条件进行模拟,我们需要使用一个变量来累加遍历的数组元素之和,这样可以判断总和是否超出了mid花费时间,假如超过了那么我们看当前有没有求助的机会假如有那么使用求助机会,求助的时候我们需要将到当前位置花费时间最大的进行求助这样才可以使得最终花费的时间最短,所以我们需要声明一个变量来记录到当前位置元素的最大值,这样当有求助机会的时候那么那么求助的为耗时最大的题目,假如用完了求助的几乎那么我们需要开启新的一天,并且将之间累计求和与记录到当前最大值的变量重置为0,这个时候我们就需要重新完成之前那道题目了
③ 其实感觉就是翻译题目的过程,最核心的是需要弄清楚使用二分查找来解决
3. 代码如下:
from typing import List
class Solution:
def check(self, time: List[int], m: int, mid: int):
# 一开始的天数肯定是为1的
days = 1
sum, maxcost, chancetohelp, i = 0, 0, 1, 0
while i < len(time):
# 找出耗时最大的
maxcost = max(maxcost, time[i])
sum += time[i]
# 超出了最大的耗时
if sum > mid:
# 有求助的机会
if chancetohelp:
# 贪心: 求助耗时最大的那一个
sum -= maxcost
chancetohelp = 0
else:
# 没有求助机会了, 需要开启新的一天
maxcost = 0
sum = 0
days += 1
chancetohelp = 1
# 重新做当前这道累加超时的题目
i -= 1
i += 1
return days <= m
def minTime(self, time: List[int], m: int) -> int:
l, r = 0, sum(time)
res = r
while l <= r:
mid = (l + r) // 2
if self.check(time, m, mid):
res = mid
r = mid - 1
else:
l = mid + 1
return res