【每日一题】1894. 找到需要补充粉笔的学生编号:一题两解:图解 & 前缀和 & 暴力 & 二分查找!
今天是我坚持写题解的第 37 天!
题目描述(Medium)
一个班级里有 n
个学生,编号为 0
到 n - 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
大了,就可以直接返回了。
请看代码:
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;
}
}
-
时间复杂度: ,最多遍历两遍数组。 -
空间复杂度: ,原地求的前缀和,不耗费额外空间。
运行结果如下:
方法二、前缀和 + 二分查找
由于前缀和是有序数组,而第二步我们相当于在有序数组中寻找第一个比 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;
}
}
-
时间复杂度: ,求前缀和过程为 ,查找的过程为 ,所以,总的时间复杂度为 。 -
空间复杂度: ,原地求的前缀和,不耗费额外空间。
运行结果如下,时间没有明显提升取决于用例:
最后
关于二分查找的模板,之前写过一篇题解,有兴趣的可以看看:
如果对你有帮助,请点个赞吧,谢谢^^
也可以关注我,每日分享题解,一起刷题,一起拿全家桶。