力扣【二分查找】:LCP 12. 小张刷题计划

  1. 题目描述:

为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。

在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。

我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。

  1. 算法思路:

题目限制在m天内完成n道题,并且要求顺序完成,每天可以使用一次场外求助,设定T为m天内最大的做题时间,问在限定天数m内完成的最小的T,

很显然,做题的最大时长T和完成n题的天数m是成反比的,那么T的范围就应该是[0, sum(time)],原因是如果题目数刚好等于天数,则可以每天求助一次,此时每天做题时长0,

如果天数为1,那么上界应该是time中除了最大值以外的所有值之和,这里简单写为sum(time),

所以很显然可以通过对[0, sum(time)]二分选择找到T,然后根据在T的最大时长的条件下,完成n题需要的天数x,如果x大于m,则说明T过小,反之则说明T以及小于T的值可能满足要求,

于是该题显然要用二分查找,但问题的难点在于如何设计二分条件的函数,即如何设计一个函数能正确计算T的最大时长下的天数x,

由于题目要求顺序做题,所以我们可以按照顺序遍历time数组并累加每天的时长,直到这个累加值大于T,便重置累加值然后将天数+1,

但是这种方法有一个问题:我们没有明确场外求助解决的是哪一题,为了最大化利用场外求助我们应该选取每天花费最长的题目作为使用场外求助的题目,

于是这就是一个贪心算法,我们可以在遍历过程中不断记录已遍历数组的最大值,然后从最大值和当前遍历元素中选取较小的一个元素作为下一次加入的对象,

这样我们就可以绕开将某一个最大值加入sum的情况,从而找到最佳结果。

这样当sum加上下一次的值大于T时,就说明我们某一天的求和已经完成,替换的最大值就是遍历过程中记录的最大值,

此时重置sum = 0,max_time为新的t即可,否则只需要累加sum和更新max_time即可,

分析上述逻辑可以发现,如果数组的最后一个元素恰好使得最后一天的做题时间大于T,那么天数+1,循环结束,

如果最后一个元素使得做题时间使得那天的时间小于等于T,则不会增加天数,结束循环,

我们可以在新的一天开始时增加天数,那么为了获得正确结果天数初始化就为1。

细节:max_time初始化为0,这样会使得第一次循环是一次无效计算,但是却可以保证后续流程用一套代码就能完成,

这和计算前缀和数组时多申请一个空间,然后将第一个空间作为空闲的思路是类似的,这样prefix的第一次计算获得的值存放位置实际在下标1除,而下标0处仅作为0而存在,

免去了另一种写法中需要单独为prefix[0]赋值的问题

  1. 代码:

时间复杂度:O(N log(C)),其中C是[0, sum(time)]的元素个数,
空间复杂度:O(1)

int minTime(vector<int>& time, int m) {
        int lower_time = 0, upper_time = std::accumulate(time.begin(), time.end(), 0);

        while (lower_time < upper_time) {
            int time_value = lower_time + ((upper_time - lower_time) >> 1);

            int days = NeededDays(time, time_value);

            if (days > m) {
                lower_time = time_value + 1;
            } else {
                upper_time = time_value;
            }
        }

        return upper_time;
    }

    int NeededDays(std::vector<int> &time, const int time_value) {
        int needed_days = 1;
        int sum = 0;
        int max_time = 0;

        for (const int &t : time) {
            int next_add = std::min(t, max_time);

            if (sum + next_add <= time_value) {
                sum += next_add;
                max_time = std::max(t, max_time);
            } else {
                sum = 0;
                ++needed_days;
                max_time = t;
            }
        }

        return needed_days;
    }
posted @ 2026-08-13 14:40  lf123z  阅读(1)  评论(0)    收藏  举报