1894. Find the Student that Will Replace the Chalk
Problem Description
In this problem, you're given an integer array chalk
and an integer k
, which represent the number of chalk pieces available for a group of students to use. Each element in the chalk
array corresponds to the amount of chalk that each student will use to solve a problem. For example, chalk[0]
is the amount of chalk the first student will use, chalk[1]
the amount for the second student, and so on, in a repeating cycle.
The sequence starts with the first student (index 0) and goes to the last student (index n - 1
). After the teacher has distributed problems to all students, the cycle starts again with the first student. This continues until there aren't enough chalk pieces for a student to use. At that point, the student will be asked to replace the chalk.
The task is to find out which student will be asked to replace the chalk. In other words, you need to return the index of the first student who cannot proceed because the chalk pieces left are fewer than what they need.
Intuition
To solve this problem, it's efficient to use a prefix sum and binary search.
Prefix Sum: First, we calculate the cumulative chalk usage for each student, which gives us a sequence where each element tells us the total amount of chalk used by all students up to that point. This can be done using the accumulate
function from the itertools
module in Python, which creates a list of accumulated sums.
Binary Search: Then, because the cycle starts again after reaching the last student, we can take the total amount of chalk k
and find the remainder when divided by the total accumulated chalk. This tells us how much chalk would be left after an integer number of full cycles, and the next student to take a problem would need more chalk than what we have.
Using this leftover amount of chalk, we can perform a binary search to find the first student who would need more chalk than the remnants. The built-in function bisect_right
from the bisect
module in Python can help us here. It searches for the place to insert the leftover chalk amount in order to maintain the sorted order. This position, effectively the index of the student, is the output of our function.
Learn more about Binary Search and Prefix Sum patterns.
Solution Approach
The solution approach for this problem is to first calculate the prefix sum of the chalk requirements for each student and then use a binary search algorithm to find the index of the student who will replace the chalk.
Here's a step-by-step breakdown of the implementation described by the provided solution code:
-
Calculate Prefix Sum: Using the
accumulate
function from theitertools
module, we calculate the cumulative sum of thechalk
array. The result is a new lists
wheres[i]
represents the total amount of chalk used by the firsti + 1
students. This step is crucial because it allows us to understand the total chalk consumption in one complete cycle through all students. -
Modulo Operation: The value of
k
may be very large, possibly larger than the total amount of chalk used in a full cycle by all students. To handle this, we use the modulo operationk %= s[-1]
which gives us the remainder ofk
after dividing by the total chalk used in one cycle (s[-1]
represents the total chalk used after all students have taken a problem once). This effectively reduces the problem to a single incomplete cycle, making it easier to find the student who will replace the chalk. -
Binary Search: Now, we need to identify the first student who cannot proceed due to insufficient chalk. This is done by using the
bisect_right
function from thebisect
module, which performs a binary search. The function finds the position where the leftover chalkk
would be inserted to maintain the sorted order of the accumulated chalk lists
. The returned indexbisect_right(s, k)
is the index of the first student whose chalk requirement exceeds the amount of leftover chalkk
.
Combining these steps, the code successfully determines the student who will replace the chalk:
class Solution:
def chalkReplacer(self, chalk: List[int], k: int) -> int:
s = list(accumulate(chalk)) # step 1
k %= s[-1] # step 2
return bisect_right(s, k) # step 3
Even though this approach appears straightforward, it's highly efficient because both the prefix sum and binary search operate in O(n)
and O(log n)
time complexities, respectively, making the whole solution quite performant for even large input sizes.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution approach with a small example to illustrate how it works in practice. Consider the chalk
array [5, 1, 3]
and k = 11
:
-
We first calculate the prefix sum, which tells us the cumulative amount of chalk used by the students in one round:
Original chalk array: [5, 1, 3] Prefix sum: [5, 6, 9]
With this, we can see that one full cycle of chalk usage by all students amounts to
9
. -
Now we apply the modulo operation to
k
with the total chalk used in one cycle:k = 11 Total chalk in one cycle = 9 (from the last element of the prefix sum) k % 9 = 2
The modulo gives
2
, so after completing one full round, we have2
chalks left. -
Using binary search, we find where
2
(the remaining chalk pieces) would be inserted in our prefix sum array to maintain the sorted order:Prefix sum array: [5, 6, 9] Leftover: 2
A binary search for
2
in the array[5, 6, 9]
would place it before the first element since2
is less than5
. Therefore, it would be inserted at index0
.
Thus, the student at index 0
(the first student) would be the one who can't proceed because they require 5
chalks, and we only have 2
remaining. Hence, the answer is 0
, the index of the first student in our chalk
array.
Solution Implementation
1# First, we need to import the required functions from the itertools and bisect modules.
2from itertools import accumulate
3from bisect import bisect_right
4
5class Solution:
6 def chalkReplacer(self, chalk: List[int], k: int) -> int:
7 # Accumulate the chalk requirements in a list
8 # to find out the total chalk needed for one round.
9 total_chalk_per_round = list(accumulate(chalk))
10
11 # The remaining chalk after multiple complete rounds
12 # is the remainder of k divided by the total chalk per round.
13 k %= total_chalk_per_round[-1]
14
15 # Find the index of the smallest number in the accumulated
16 # chalk list that is greater than k using binary search.
17 # This is the index of the student who will be the next to receive chalk.
18 return bisect_right(total_chalk_per_round, k)
19
20# We need to import the typing module to use List[int] as a type hint in the function signature
21from typing import List
22
1class Solution {
2 public int chalkReplacer(int[] chalk, int k) {
3 int numberOfStudents = chalk.length;
4
5 // Define a prefix sum array with an extra space for easier calculations
6 long[] prefixSum = new long[numberOfStudents + 1];
7
8 // Calculate the prefix sum of chalk requirements
9 // Here, starting index of prefixSum should be 1 to match the chalk indices.
10 prefixSum[1] = chalk[0];
11 for (int i = 1; i < numberOfStudents; ++i) {
12 prefixSum[i + 1] = prefixSum[i] + chalk[i];
13 }
14
15 // Find the remaining chalk after it has been distributed once
16 k %= prefixSum[numberOfStudents];
17
18 // Initialize search boundaries for binary search
19 int left = 0, right = numberOfStudents;
20
21 // Performing binary search to find the minimum index 'left'
22 // such that prefixSum[left] is greater than 'k'
23 while (left < right) {
24 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2 but more efficient
25
26 // If the sum up to mid is greater than k, search the left half
27 if (prefixSum[mid] > k) {
28 right = mid;
29 } else { // Otherwise, search the right half
30 left = mid + 1;
31 }
32 }
33
34 // The index 'left' points to the first student that cannot finish
35 // their chalk round, that's the student to start the next round
36 return left - 1; // Adjusting the index because prefixSum started from 1 instead of 0
37 }
38}
39
1#include <vector>
2#include <algorithm> // Needed for std::upper_bound
3
4class Solution {
5public:
6 // Function to find the index of the student who will receive the last chalk piece before the chalk runs out
7 int chalkReplacer(vector<int>& chalk, int k) {
8 int numStudents = chalk.size();
9 vector<long long> prefixSum(numStudents, chalk[0]);
10 // Calculate prefix sums of chalk requirements
11 for (int i = 1; i < numStudents; ++i) {
12 prefixSum[i] = prefixSum[i - 1] + chalk[i];
13 }
14 // The amount of chalk K is now how much chalk will be needed in the last round
15 // because we are interested in a full cycle where every student has received chalk exactly once
16 k %= prefixSum[numStudents - 1];
17
18 // Use upper bound to return the first element in the prefix sum which is greater than k
19 // this will be the first student who will not be able to finish writing as the chalk will run out before this student
20 return std::upper_bound(prefixSum.begin(), prefixSum.end(), k) - prefixSum.begin();
21 }
22};
23
1// Import the necessary functions from `lodash` for calculating the prefix sums and finding the upper bound.
2import _ from 'lodash';
3
4// Function to calculate prefix sums of an array.
5const calculatePrefixSums = (chalk: number[]): number[] => {
6 const prefixSums: number[] = [];
7 chalk.reduce((acc, current) => {
8 const sum = acc + current;
9 prefixSums.push(sum);
10 return sum;
11 }, 0);
12 return prefixSums;
13};
14
15// Main function to find the index of the student who will receive the last chalk piece before the chalk runs out.
16const chalkReplacer = (chalk: number[], k: number): number => {
17 const numStudents = chalk.length;
18 const prefixSums: number[] = calculatePrefixSums(chalk);
19 // Adjust the number of chalk (k) by taking modulus with the total sum to find remainder chalk after full cycles.
20 k %= prefixSums[prefixSums.length - 1];
21
22 // Use lodash's sortedIndex to find the smallest index at which k should be inserted
23 // into `prefixSums` in order to maintain the array's sorted order.
24 const studentIndex: number = _.sortedIndex(prefixSums, k);
25
26 // This index represents the student who cannot finish writing as the chalk runs out before their turn.
27 return studentIndex;
28};
29
30export { chalkReplacer };
31
Time and Space Complexity
Time Complexity
The time complexity of the given code is determined by two main operations: accumulating the chalk
array and performing the binary search.
- The
accumulate
function computes the prefix sums of thechalk
list, which takesO(n)
time, wheren
is the length ofchalk
. - The modulus operation
k %= s[-1]
is constant time,O(1)
. bisect_right
is a binary search function, which takesO(log n)
time to find the insert position fork
in the sorted lists
.
Combining these complexities, we get O(n + log n)
which simplifies to O(n)
since O(n)
dominates O(log n)
.
Space Complexity
The space complexity of the code comes from storing the prefix sums in the list s
. Since this list has the same length as the input list chalk
, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!