vlambda博客
学习文章列表

【每日一题】1894. 找到需要补充粉笔的学生编号:一题两解:图解 & 前缀和 & 暴力 & 二分查找!

今天是我坚持写题解的第 37 天!

题目描述(Medium)

一个班级里有 n 个学生,编号为 0n - 1 。每个学生会依次回答问题,编号为 0 的学生先回答,然后是编号为 1 的学生,以此类推,直到编号为 n - 1 的学生,然后老师会重复这个过程,重新从编号为 0 的学生开始回答问题。

给你一个长度为 n 且下标从 0 开始的整数数组 chalk 和一个整数 k 。一开始粉笔盒里总共有 k 支粉笔。当编号为 i 的学生回答问题时,他会消耗 chalk[i] 支粉笔。如果剩余粉笔数量 严格小于 chalk[i] ,那么学生 i需要 补充 粉笔。

请你返回需要 补充 粉笔的学生 编号 。

示例 1:

输入:chalk = [5,1,5], k = 22

输出:0 

解释:学生消耗粉笔情况如下:

  • 编号为 0 的学生使用 5 支粉笔,然后 k = 17 。
  • 编号为 1 的学生使用 1 支粉笔,然后 k = 16 。
  • 编号为 2 的学生使用 5 支粉笔,然后 k = 11 。
  • 编号为 0 的学生使用 5 支粉笔,然后 k = 6 。
  • 编号为 1 的学生使用 1 支粉笔,然后 k = 5 。
  • 编号为 2 的学生使用 5 支粉笔,然后 k = 0 。
  • 编号为 0 的学生没有足够的粉笔,所以他需要补充粉笔。

示例 2:

输入:chalk = [3,4,1,2], k = 25

输出:1

解释:学生消耗粉笔情况如下:

  • 编号为 0 的学生使用 3 支粉笔,然后 k = 22 。
  • 编号为 1 的学生使用 4 支粉笔,然后 k = 18 。
  • 编号为 2 的学生使用 1 支粉笔,然后 k = 17 。
  • 编号为 3 的学生使用 2 支粉笔,然后 k = 15 。
  • 编号为 0 的学生使用 3 支粉笔,然后 k = 12 。
  • 编号为 1 的学生使用 4 支粉笔,然后 k = 8 。
  • 编号为 2 的学生使用 1 支粉笔,然后 k = 7 。
  • 编号为 3 的学生使用 2 支粉笔,然后 k = 5 。
  • 编号为 0 的学生使用 3 支粉笔,然后 k = 2 。
  • 编号为 1 的学生没有足够的粉笔,所以他需要补充粉笔。

提示:

chalk.length == n
1 <= n <= 105
1 <= chalk[i] <= 105
1 <= k <= 109

链接:https://leetcode-cn.com/problems/find-the-student-that-will-replace-the-chalk

方法一、前缀和 + 暴力

这道题很简单,直接暴力求解也是可以的,但是,仔细思考如果 k 特别大的情况,远远超出了数组元素之和,这样会导致要循环很多轮数组,所以,我们可以先求出数组之和,看 k 是多少轮数组之和,直接取余数,再从头遍历即可。当然,在第一次遍历的过程中,我们可以直接求出前缀和,与 k 进行比较,如果到某一个元素比 k 大了,就可以直接返回了。

【每日一题】1894. 找到需要补充粉笔的学生编号:一题两解:图解 & 前缀和 & 暴力 & 二分查找!
image-20210910091450569

请看代码:

class Solution {
    public int chalkReplacer(int[] chalk, int k) {
        // 先求出前缀和
        for (int i = 0; i < chalk.length; i++) {
            chalk[i] = chalk[i] + (i - 1 < 0 ? 0 : chalk[i - 1]);
            // 求前缀和的过程中比k大了,直接返回
            if (chalk[i] > k) {
                return i;
            }
        }

        // k对总和取余,前缀和最后一个元素就是总和
        // 这样相当于跳过了 k/sum 轮
        k = k % chalk[chalk.length - 1];
        for (int i = 0; i < chalk.length; i++) {
            if (chalk[i] > k) {
                return i;
            }
        }

        return -1;
    }
}
  • 时间复杂度: ,最多遍历两遍数组。
  • 空间复杂度: ,原地求的前缀和,不耗费额外空间。

运行结果如下:

【每日一题】1894. 找到需要补充粉笔的学生编号:一题两解:图解 & 前缀和 & 暴力 & 二分查找!
image-20210910091854395

方法二、前缀和 + 二分查找

由于前缀和是有序数组,而第二步我们相当于在有序数组中寻找第一个比 k 大的数,所以,也可以使用二分查找。

请看代码:

class Solution {
    public int chalkReplacer(int[] chalk, int k) {
        // 先求出前缀和
        for (int i = 0; i < chalk.length; i++) {
            chalk[i] = chalk[i] + (i - 1 < 0 ? 0 : chalk[i - 1]);
            // 求前缀和的过程中比k大了,直接返回
            if (chalk[i] > k) {
                return i;
            }
        }

        // k对总和取余,前缀和最后一个元素就是总和
        // 这样相当于跳过了 k/sum 轮
        k = k % chalk[chalk.length - 1];
        int left = 0, right = chalk.length - 1;
        while (left < right) {
            int mid = (left + right) / 2;
            // 严格比k大,所以这里不要加等号
            if (chalk[mid] > k) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }

        return right;
    }
}
  • 时间复杂度: ,求前缀和过程为 ,查找的过程为 ,所以,总的时间复杂度为
  • 空间复杂度: ,原地求的前缀和,不耗费额外空间。

运行结果如下,时间没有明显提升取决于用例:

image-20210910092734117

最后

关于二分查找的模板,之前写过一篇题解,有兴趣的可以看看:


如果对你有帮助,请点个赞吧,谢谢^^

也可以关注我,每日分享题解,一起刷题,一起拿全家桶。

彤哥来刷题啦
刷题是一种信仰,来啊,一起来刷题。
52篇原创内容
Official Account