1176. Diet Plan Performance 🔒
Problem Description
A dieter tracks their daily calorie consumption over multiple days. You're given an array calories
where calories[i]
represents the number of calories consumed on day i
.
The dieter evaluates their performance by looking at every consecutive k
-day window in their diet. For each window of k
consecutive days, they calculate the total calories T
consumed during that period.
Based on the total T
, they assign points as follows:
- If
T < lower
: Poor performance, lose 1 point - If
T > upper
: Good performance, gain 1 point - If
lower ≤ T ≤ upper
: Normal performance, no change in points
The dieter starts with 0 points. Your task is to calculate the final total points after evaluating all possible k
-day windows.
For example, if calories = [1,2,3,4,5]
and k = 2
, you would evaluate:
- Days 0-1: total = 1+2 = 3
- Days 1-2: total = 2+3 = 5
- Days 2-3: total = 3+4 = 7
- Days 3-4: total = 4+5 = 9
Each of these totals would be compared against lower
and upper
to determine points gained or lost.
The solution uses a prefix sum array to efficiently calculate the sum of any k
-day window. The prefix sum s[i]
stores the total calories from day 0 to day i-1
. This allows us to calculate any window sum as s[i+k] - s[i]
, which gives the total calories from day i
to day i+k-1
.
Intuition
The key observation is that we need to calculate the sum of calories for every consecutive k
-day window. A naive approach would be to recalculate the sum for each window from scratch, which would involve adding k
numbers for each of the n-k+1
windows, resulting in O(n*k) time complexity.
However, we can notice that consecutive windows overlap significantly. For example, if we have windows [day 0, day 1, day 2] and [day 1, day 2, day 3], they share days 1 and 2. Recalculating the entire sum each time is redundant.
This naturally leads us to think about prefix sums - a technique where we precompute cumulative sums. If we know the total calories consumed from day 0 to any day i
, we can quickly find the sum of any range.
The prefix sum array s
is constructed where s[i]
represents the total calories from day 0 to day i-1
. With this preprocessing:
- To get the sum from day
i
to dayi+k-1
, we simply calculates[i+k] - s[i]
- This works because
s[i+k]
contains the sum up to dayi+k-1
, ands[i]
contains the sum up to dayi-1
- Subtracting them gives us exactly the calories for days
i
throughi+k-1
This reduces our window sum calculation from O(k) to O(1), making the overall algorithm O(n) after the O(n) preprocessing step. Once we have each window's sum, comparing it with lower
and upper
to update points is straightforward.
Learn more about Sliding Window patterns.
Solution Approach
The solution implements the prefix sum technique to efficiently calculate the sum of each k
-day window.
Step 1: Build the Prefix Sum Array
We use Python's accumulate
function with initial=0
to create a prefix sum array s
of length n+1
:
s = list(accumulate(calories, initial=0))
This creates an array where:
s[0] = 0
(no calories consumed before day 0)s[1] = calories[0]
s[2] = calories[0] + calories[1]
s[i] = calories[0] + calories[1] + ... + calories[i-1]
Step 2: Iterate Through All k-Day Windows
We iterate from i = 0
to i = n - k
(inclusive), which covers all possible starting positions for a k
-day window:
for i in range(n - k + 1):
Step 3: Calculate Window Sum Using Prefix Sum
For each window starting at position i
, we calculate the total calories:
t = s[i + k] - s[i]
This gives us the sum of calories[i] + calories[i+1] + ... + calories[i+k-1]
in O(1) time.
Step 4: Update Points Based on Performance
We compare the window sum t
with the thresholds:
if t < lower: ans -= 1 elif t > upper: ans += 1
- If
t < lower
: Subtract 1 point (poor performance) - If
t > upper
: Add 1 point (good performance) - Otherwise: No change (normal performance)
Step 5: Return Final Points
After evaluating all windows, we return the accumulated points ans
.
The time complexity is O(n) for building the prefix sum array plus O(n-k+1) for evaluating windows, giving us O(n) overall. The space complexity is O(n) for storing the prefix sum array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
calories = [3, 2, 5, 1, 4]
k = 3
(evaluate 3-day windows)lower = 6
upper = 10
Step 1: Build the Prefix Sum Array
We create the prefix sum array s
:
s[0] = 0
(initial value)s[1] = 0 + 3 = 3
s[2] = 3 + 2 = 5
s[3] = 5 + 5 = 10
s[4] = 10 + 1 = 11
s[5] = 11 + 4 = 15
So s = [0, 3, 5, 10, 11, 15]
Step 2-4: Evaluate Each 3-Day Window
Starting with ans = 0
:
Window 1: Days 0-2 (calories[0:3] = [3, 2, 5])
- Calculate sum:
t = s[0+3] - s[0] = s[3] - s[0] = 10 - 0 = 10
- Compare:
10
is not less than6
and not greater than10
, so it's in range [6, 10] - Points: No change,
ans = 0
Window 2: Days 1-3 (calories[1:4] = [2, 5, 1])
- Calculate sum:
t = s[1+3] - s[1] = s[4] - s[1] = 11 - 3 = 8
- Compare:
8
is in range [6, 10] - Points: No change,
ans = 0
Window 3: Days 2-4 (calories[2:5] = [5, 1, 4])
- Calculate sum:
t = s[2+3] - s[2] = s[5] - s[2] = 15 - 5 = 10
- Compare:
10
is in range [6, 10] - Points: No change,
ans = 0
Final Result: The dieter scores 0
points total.
If we had different thresholds, say lower = 7
and upper = 9
, the results would be:
- Window 1:
t = 10
, which is> 9
, soans = 1
(good performance) - Window 2:
t = 8
, which is in [7, 9], soans = 1
(no change) - Window 3:
t = 10
, which is> 9
, soans = 2
(good performance)
The prefix sum technique allows us to calculate each window sum in O(1) time instead of repeatedly adding k elements.
Solution Implementation
1from typing import List
2from itertools import accumulate
3
4
5class Solution:
6 def dietPlanPerformance(
7 self, calories: List[int], k: int, lower: int, upper: int
8 ) -> int:
9 """
10 Calculate diet plan performance score based on calorie consumption.
11
12 Args:
13 calories: List of daily calorie consumption
14 k: Number of consecutive days to evaluate
15 lower: Lower threshold for calorie sum
16 upper: Upper threshold for calorie sum
17
18 Returns:
19 Performance score (points gained minus points lost)
20 """
21 # Create prefix sum array with initial 0 for easier calculation
22 # prefix_sums[i] represents sum of calories[0:i]
23 prefix_sums = list(accumulate(calories, initial=0))
24
25 # Initialize score and get total number of days
26 score = 0
27 total_days = len(calories)
28
29 # Iterate through all possible k-day windows
30 for start_day in range(total_days - k + 1):
31 # Calculate sum of calories for current k-day window
32 # Using prefix sum: sum(calories[i:i+k]) = prefix_sums[i+k] - prefix_sums[i]
33 window_sum = prefix_sums[start_day + k] - prefix_sums[start_day]
34
35 # Update score based on threshold comparison
36 if window_sum < lower:
37 score -= 1 # Lose a point for under-consumption
38 elif window_sum > upper:
39 score += 1 # Gain a point for over-consumption
40
41 return score
42
1class Solution {
2 public int dietPlanPerformance(int[] calories, int k, int lower, int upper) {
3 int n = calories.length;
4
5 // Build prefix sum array where prefixSum[i] represents sum of elements from index 0 to i-1
6 int[] prefixSum = new int[n + 1];
7 for (int i = 0; i < n; i++) {
8 prefixSum[i + 1] = prefixSum[i] + calories[i];
9 }
10
11 // Initialize points counter
12 int points = 0;
13
14 // Iterate through all possible k-day windows
15 for (int i = 0; i <= n - k; i++) {
16 // Calculate sum of calories for current k-day window using prefix sum
17 // Sum from index i to i+k-1 = prefixSum[i+k] - prefixSum[i]
18 int windowSum = prefixSum[i + k] - prefixSum[i];
19
20 // Award points based on performance
21 if (windowSum < lower) {
22 // Poor performance: subtract one point
23 points--;
24 } else if (windowSum > upper) {
25 // Excellent performance: add one point
26 points++;
27 }
28 // If windowSum is between lower and upper (inclusive), no points are awarded
29 }
30
31 return points;
32 }
33}
34
1class Solution {
2public:
3 int dietPlanPerformance(vector<int>& calories, int k, int lower, int upper) {
4 int n = calories.size();
5
6 // Create prefix sum array to efficiently calculate sum of k consecutive days
7 // prefixSum[i] stores the sum of calories from index 0 to i-1
8 vector<int> prefixSum(n + 1, 0);
9
10 // Build the prefix sum array
11 for (int i = 0; i < n; ++i) {
12 prefixSum[i + 1] = prefixSum[i] + calories[i];
13 }
14
15 // Initialize points counter
16 int points = 0;
17
18 // Iterate through all possible k-day windows
19 for (int i = 0; i <= n - k; ++i) {
20 // Calculate total calories for current k-day window using prefix sum
21 // Sum from index i to i+k-1 = prefixSum[i+k] - prefixSum[i]
22 int totalCalories = prefixSum[i + k] - prefixSum[i];
23
24 // Update points based on performance rules
25 if (totalCalories < lower) {
26 // Poor performance: subtract 1 point
27 --points;
28 } else if (totalCalories > upper) {
29 // Great performance: add 1 point
30 ++points;
31 }
32 // If totalCalories is between lower and upper (inclusive), no points change
33 }
34
35 return points;
36 }
37};
38
1/**
2 * Calculates diet plan performance score based on calorie consumption over consecutive days
3 * @param calories - Array of daily calorie consumption
4 * @param k - Number of consecutive days to evaluate
5 * @param lower - Lower threshold for calorie sum
6 * @param upper - Upper threshold for calorie sum
7 * @returns Performance score (negative for under-eating, positive for over-eating)
8 */
9function dietPlanPerformance(calories: number[], k: number, lower: number, upper: number): number {
10 const totalDays: number = calories.length;
11
12 // Build prefix sum array for efficient range sum calculation
13 // prefixSum[i] represents sum of calories from day 0 to day i-1
14 const prefixSum: number[] = new Array(totalDays + 1).fill(0);
15 for (let i = 0; i < totalDays; i++) {
16 prefixSum[i + 1] = prefixSum[i] + calories[i];
17 }
18
19 let performanceScore: number = 0;
20
21 // Evaluate each k-day window
22 for (let startDay = 0; startDay <= totalDays - k; startDay++) {
23 // Calculate sum of calories for current k-day window using prefix sum
24 const windowCalorieSum: number = prefixSum[startDay + k] - prefixSum[startDay];
25
26 // Update performance score based on thresholds
27 if (windowCalorieSum < lower) {
28 performanceScore--; // Penalty for under-eating
29 } else if (windowCalorieSum > upper) {
30 performanceScore++; // Penalty for over-eating
31 }
32 // No change if within healthy range [lower, upper]
33 }
34
35 return performanceScore;
36}
37
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the calories
array. This is because:
- The
accumulate
function iterates through alln
elements once to build the prefix sum array, which takesO(n)
time - The for loop iterates
n - k + 1
times, which isO(n)
in the worst case - Each iteration of the loop performs constant time operations: array lookups, subtraction, and comparisons
- Therefore, the overall time complexity is
O(n) + O(n) = O(n)
The space complexity is O(n)
, where n
is the length of the calories
array. This is because:
- The prefix sum array
s
storesn + 1
elements (including the initial 0), requiringO(n)
space - The other variables (
ans
,n
,i
,t
) use constant spaceO(1)
- Therefore, the overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors in Window Iteration
Pitfall: A common mistake is incorrectly calculating the range for iterating through k-day windows. Developers might write:
range(n - k)
instead ofrange(n - k + 1)
, which misses the last valid windowrange(n)
which causes index out of bounds when accessingprefix_sums[i + k]
Example of the mistake:
# WRONG: Misses the last valid window
for i in range(len(calories) - k): # Should be len(calories) - k + 1
window_sum = prefix_sums[i + k] - prefix_sums[i]
Solution: Always use range(n - k + 1)
where n is the length of the calories array. This ensures you check all valid windows including the last one.
2. Incorrect Prefix Sum Indexing
Pitfall: Confusion about prefix sum array indexing, especially when not using initial=0
. Without the initial zero, developers might incorrectly calculate window sums.
Example of the mistake:
# WRONG: Without initial=0, indexing becomes confusing
prefix_sums = list(accumulate(calories)) # No initial=0
# Now prefix_sums[i] represents sum up to and including calories[i]
# This requires different indexing logic:
window_sum = prefix_sums[i + k - 1] - (prefix_sums[i - 1] if i > 0 else 0)
Solution: Always use accumulate(calories, initial=0)
to maintain consistent indexing where prefix_sums[i]
represents the sum of the first i
elements, making the window sum calculation straightforward: prefix_sums[i + k] - prefix_sums[i]
.
3. Edge Case: k Equals Array Length
Pitfall: Not handling the case where k
equals the length of the calories array properly, or assuming there will always be multiple windows to evaluate.
Example scenario:
calories = [100, 200, 300] k = 3 # Only one window possible
Solution: The code naturally handles this case when using range(n - k + 1)
, which becomes range(1)
, evaluating exactly one window. However, always verify this edge case in testing.
4. Misunderstanding Threshold Comparisons
Pitfall: Using incorrect comparison operators or including equality in the wrong conditions.
Example of the mistake:
# WRONG: Including equality in both conditions if window_sum <= lower: # Should be < lower score -= 1 elif window_sum >= upper: # Should be > upper score += 1
Solution: Remember the exact conditions:
- Lose point:
window_sum < lower
(strictly less than) - Gain point:
window_sum > upper
(strictly greater than) - No change:
lower <= window_sum <= upper
(inclusive range)
What's the relationship between a tree and a graph?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview 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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!