1124. Longest Well-Performing Interval
Problem Description
You are given an array hours
that represents the number of hours an employee worked each day.
A day is classified as a tiring day if the employee worked strictly more than 8 hours that day.
A well-performing interval is a consecutive sequence of days where the number of tiring days is strictly greater than the number of non-tiring days.
Your task is to find the length of the longest well-performing interval.
For example, if hours = [9, 9, 6, 0, 6, 6, 9]
:
- Days with hours > 8 are tiring days: positions 0, 1, and 6 (values 9, 9, 9)
- Days with hours ≤ 8 are non-tiring days: positions 2, 3, 4, 5 (values 6, 0, 6, 6)
- The interval from index 0 to 2 has 2 tiring days and 1 non-tiring day, making it well-performing
- The entire array from index 0 to 6 has 3 tiring days and 4 non-tiring days, which is not well-performing
The goal is to find the maximum length among all possible well-performing intervals.
Intuition
The key insight is to transform this problem into finding the longest subarray with a positive sum. We can map each tiring day (hours > 8) to +1
and each non-tiring day to -1
. A well-performing interval then becomes a subarray where the sum is positive (more +1
s than -1
s).
To find the longest such subarray efficiently, we use prefix sums. Let s
be the running sum from the start of the array. If at any point s > 0
, the entire array from index 0 to the current position forms a well-performing interval.
But what if s ≤ 0
? We need to find an earlier position where removing that prefix would give us a positive sum. If the current prefix sum is s
, we want to find the earliest position j
where the prefix sum was s - 1
. Why s - 1
? Because removing a prefix with sum s - 1
from a current sum of s
gives us a subarray with sum s - (s - 1) = 1
, which is positive.
For example, if at position i
we have s = -2
, and we previously saw s = -3
at position j
, then the subarray from j + 1
to i
has sum -2 - (-3) = 1
, making it a well-performing interval.
We use a hash table to store the first occurrence of each prefix sum value. This allows us to quickly find the earliest position with sum s - 1
and calculate the maximum interval length. We only store the first occurrence because we want the longest possible interval, which means starting as early as possible.
Learn more about Stack, Prefix Sum and Monotonic Stack patterns.
Solution Approach
We implement the solution using prefix sum and a hash table to track the first occurrence of each prefix sum value.
Step-by-step implementation:
-
Initialize variables:
ans = 0
: stores the maximum length of well-performing intervals = 0
: running prefix sum (difference between tiring and non-tiring days)pos = {}
: hash table to store the first occurrence index of each prefix sum
-
Transform and process the array: For each day at index
i
withhours[i]
:- If
hours[i] > 8
, increments
by 1 (tiring day contributes +1) - Otherwise, decrement
s
by 1 (non-tiring day contributes -1)
- If
-
Check for well-performing intervals:
-
Case 1: If
s > 0
, the entire segment from index 0 to current indexi
is well-performing. Updateans = i + 1
. -
Case 2: If
s ≤ 0
, check ifs - 1
exists in the hash tablepos
. If it does:- Let
j = pos[s - 1]
be the index where prefix sum wass - 1
- The segment from index
j + 1
toi
has sum =s - (s - 1) = 1 > 0
- Update
ans = max(ans, i - j)
- Let
-
-
Record first occurrence: If the current prefix sum
s
hasn't been seen before, record it in the hash table:pos[s] = i
. We only store the first occurrence to maximize the interval length.
Example walkthrough:
For hours = [9, 9, 6, 0, 6, 6, 9]
:
- Index 0:
s = 1
,s > 0
, soans = 1
- Index 1:
s = 2
,s > 0
, soans = 2
- Index 2:
s = 1
,s > 0
, soans = 3
- Index 3:
s = 0
, checks - 1 = -1
(not in pos) - Index 4:
s = -1
, checks - 1 = -2
(not in pos) - Index 5:
s = -2
, checks - 1 = -3
(not in pos) - Index 6:
s = -1
, checks - 1 = -2
(exists at index 5),ans = max(3, 6 - 5) = 3
The algorithm runs in O(n)
time with O(n)
space for the hash table.
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 small example with hours = [6, 9, 9, 0, 9]
.
Step 1: Transform to +1/-1 array
- 6 ≤ 8 → -1 (non-tiring)
- 9 > 8 → +1 (tiring)
- 9 > 8 → +1 (tiring)
- 0 ≤ 8 → -1 (non-tiring)
- 9 > 8 → +1 (tiring)
Transformed: [-1, +1, +1, -1, +1]
Step 2: Process each element with prefix sum
Initialize: ans = 0
, s = 0
, pos = {}
Index 0 (hours[0] = 6):
- Update:
s = 0 + (-1) = -1
- Check:
s = -1 ≤ 0
, so look fors - 1 = -2
in pos -2
not found in pos- Record:
pos[-1] = 0
(first occurrence of sum -1) - Current
ans = 0
Index 1 (hours[1] = 9):
- Update:
s = -1 + 1 = 0
- Check:
s = 0 ≤ 0
, so look fors - 1 = -1
in pos -1
found at index 0! Interval from index 1 to 1 has length1 - 0 = 1
- Update:
ans = max(0, 1) = 1
- Record:
pos[0] = 1
(first occurrence of sum 0)
Index 2 (hours[2] = 9):
- Update:
s = 0 + 1 = 1
- Check:
s = 1 > 0
, entire array from start is well-performing - Update:
ans = max(1, 2 + 1) = 3
- Record:
pos[1] = 2
(first occurrence of sum 1)
Index 3 (hours[3] = 0):
- Update:
s = 1 + (-1) = 0
- Check:
s = 0 ≤ 0
, so look fors - 1 = -1
in pos -1
found at index 0! Interval from index 1 to 3 has length3 - 0 = 3
- Update:
ans = max(3, 3) = 3
pos[0]
already exists, don't update
Index 4 (hours[4] = 9):
- Update:
s = 0 + 1 = 1
- Check:
s = 1 > 0
, entire array from start is well-performing - Update:
ans = max(3, 4 + 1) = 5
pos[1]
already exists, don't update
Final Answer: 5
The longest well-performing interval is the entire array [6, 9, 9, 0, 9] with 3 tiring days and 2 non-tiring days.
Solution Implementation
1class Solution:
2 def longestWPI(self, hours: List[int]) -> int:
3 # Initialize the maximum length of well-performing interval
4 max_length = 0
5
6 # Running prefix sum: +1 for tiring days (>8 hours), -1 for non-tiring days
7 prefix_sum = 0
8
9 # Dictionary to store the first occurrence index of each prefix sum value
10 # Key: prefix sum value, Value: first index where this sum appears
11 first_occurrence = {}
12
13 # Iterate through each day's working hours
14 for current_index, work_hours in enumerate(hours):
15 # Update prefix sum: increment for tiring day, decrement for non-tiring day
16 if work_hours > 8:
17 prefix_sum += 1
18 else:
19 prefix_sum -= 1
20
21 # Case 1: If prefix sum is positive, the entire interval [0, current_index] is well-performing
22 # This means more tiring days than non-tiring days from the start
23 if prefix_sum > 0:
24 max_length = current_index + 1
25
26 # Case 2: Check if we can find a valid subarray ending at current_index
27 # We look for (prefix_sum - 1) because we want the subarray sum to be positive
28 # If subarray [j+1, i] has sum > 0, then prefix_sum[i] - prefix_sum[j] > 0
29 # So we need prefix_sum[j] < prefix_sum[i], and (prefix_sum - 1) is the largest such value
30 elif prefix_sum - 1 in first_occurrence:
31 max_length = max(max_length, current_index - first_occurrence[prefix_sum - 1])
32
33 # Store the first occurrence of this prefix sum value
34 # We only store the first occurrence because we want the longest possible interval
35 if prefix_sum not in first_occurrence:
36 first_occurrence[prefix_sum] = current_index
37
38 return max_length
39
1class Solution {
2 public int longestWPI(int[] hours) {
3 int maxLength = 0; // Maximum length of well-performing interval found
4 int prefixSum = 0; // Running prefix sum (tiring days: +1, non-tiring days: -1)
5
6 // Map to store the first occurrence index of each prefix sum value
7 Map<Integer, Integer> firstOccurrenceMap = new HashMap<>();
8
9 for (int currentIndex = 0; currentIndex < hours.length; currentIndex++) {
10 // Update prefix sum: +1 for tiring day (>8 hours), -1 for non-tiring day
11 prefixSum += hours[currentIndex] > 8 ? 1 : -1;
12
13 // Case 1: If prefix sum > 0, all days from start to current form a valid interval
14 if (prefixSum > 0) {
15 maxLength = currentIndex + 1;
16 }
17 // Case 2: Look for the longest subarray with sum > 0
18 // We need prefixSum[currentIndex] - prefixSum[j] > 0
19 // Which means prefixSum[j] < prefixSum[currentIndex]
20 // The optimal j has prefixSum[j] = prefixSum[currentIndex] - 1
21 else if (firstOccurrenceMap.containsKey(prefixSum - 1)) {
22 int intervalLength = currentIndex - firstOccurrenceMap.get(prefixSum - 1);
23 maxLength = Math.max(maxLength, intervalLength);
24 }
25
26 // Store the first occurrence of this prefix sum value
27 firstOccurrenceMap.putIfAbsent(prefixSum, currentIndex);
28 }
29
30 return maxLength;
31 }
32}
33
1class Solution {
2public:
3 int longestWPI(vector<int>& hours) {
4 int maxLength = 0; // Maximum length of well-performing interval
5 int prefixSum = 0; // Running prefix sum (tiring days as +1, non-tiring as -1)
6
7 // Map to store the first occurrence index of each prefix sum value
8 unordered_map<int, int> firstOccurrence;
9
10 for (int i = 0; i < hours.size(); ++i) {
11 // Transform hours: >8 hours is tiring (+1), ≤8 hours is non-tiring (-1)
12 prefixSum += (hours[i] > 8) ? 1 : -1;
13
14 // If prefix sum is positive, the entire subarray from index 0 to i is well-performing
15 if (prefixSum > 0) {
16 maxLength = i + 1;
17 }
18 // Look for the longest subarray with sum > 0
19 // We need prefixSum[i] - prefixSum[j] > 0, which means prefixSum[j] < prefixSum[i]
20 // To maximize length, we look for prefixSum[j] = prefixSum[i] - 1
21 else if (firstOccurrence.count(prefixSum - 1)) {
22 maxLength = max(maxLength, i - firstOccurrence[prefixSum - 1]);
23 }
24
25 // Store the first occurrence of this prefix sum value
26 if (!firstOccurrence.count(prefixSum)) {
27 firstOccurrence[prefixSum] = i;
28 }
29 }
30
31 return maxLength;
32 }
33};
34
1function longestWPI(hours: number[]): number {
2 let maxLength = 0; // Maximum length of well-performing interval
3 let prefixSum = 0; // Running prefix sum (tiring days as +1, non-tiring as -1)
4
5 // Map to store the first occurrence index of each prefix sum value
6 const firstOccurrence: Map<number, number> = new Map();
7
8 for (let i = 0; i < hours.length; i++) {
9 // Transform hours: >8 hours is tiring (+1), ≤8 hours is non-tiring (-1)
10 prefixSum += hours[i] > 8 ? 1 : -1;
11
12 // If prefix sum is positive, the entire subarray from index 0 to i is well-performing
13 if (prefixSum > 0) {
14 maxLength = i + 1;
15 }
16 // Look for the longest subarray with sum > 0
17 // We need prefixSum[i] - prefixSum[j] > 0, which means prefixSum[j] < prefixSum[i]
18 // To maximize length, we look for prefixSum[j] = prefixSum[i] - 1
19 else if (firstOccurrence.has(prefixSum - 1)) {
20 maxLength = Math.max(maxLength, i - firstOccurrence.get(prefixSum - 1)!);
21 }
22
23 // Store the first occurrence of this prefix sum value
24 if (!firstOccurrence.has(prefixSum)) {
25 firstOccurrence.set(prefixSum, i);
26 }
27 }
28
29 return maxLength;
30}
31
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the hours
array exactly once using a single for loop. Within each iteration, all operations are constant time:
- Calculating the cumulative sum
s
takesO(1)
- Checking if
s > 0
takesO(1)
- Dictionary lookup (
s - 1 in pos
) takesO(1)
on average - Dictionary insertion (
pos[s] = i
) takesO(1)
on average - The
max()
comparison takesO(1)
Since we perform n
iterations with O(1)
operations in each iteration, the overall time complexity is O(n)
.
Space Complexity: O(n)
The space complexity is determined by the pos
dictionary, which stores the first occurrence index of each unique cumulative sum value. In the worst case, every position in the array could produce a different cumulative sum value (for example, when all hours are greater than 8, the cumulative sums would be 1, 2, 3, ..., n). This means the dictionary could store up to n
key-value pairs, resulting in O(n)
space complexity. The remaining variables (ans
, s
, i
, x
) use constant space O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Checking for All Possible Prefix Sum Differences
The Mistake:
A common error is trying to check for all prefix sum values less than the current prefix sum, not just prefix_sum - 1
. Developers might write:
# INCORRECT approach
for prev_sum in range(prefix_sum - 1, min_prefix_seen - 1, -1):
if prev_sum in first_occurrence:
max_length = max(max_length, current_index - first_occurrence[prev_sum])
Why It's Wrong:
This unnecessarily increases time complexity and doesn't provide any additional benefit. The key insight is that we only need to check prefix_sum - 1
because:
- We want the minimum previous prefix sum that makes the subarray sum positive
- If
prefix_sum[i] - prefix_sum[j] > 0
, the smallest validprefix_sum[j]
isprefix_sum[i] - 1
- Checking smaller values would give shorter intervals, not longer ones
The Solution:
Stick to checking only prefix_sum - 1
as shown in the correct implementation.
Pitfall 2: Updating First Occurrence Dictionary Incorrectly
The Mistake: Updating the dictionary for every prefix sum value, even if it already exists:
# INCORRECT - overwrites existing values first_occurrence[prefix_sum] = current_index # Always updates
Why It's Wrong: This would store the last occurrence instead of the first occurrence of each prefix sum. Since we want the longest possible interval, we need the earliest (first) occurrence of each prefix sum value.
The Solution: Only update when the prefix sum hasn't been seen before:
# CORRECT - preserves first occurrence if prefix_sum not in first_occurrence: first_occurrence[prefix_sum] = current_index
Pitfall 3: Forgetting to Handle the Case When Prefix Sum > 0
The Mistake: Only checking for existing prefix sums in the dictionary without considering the special case:
# INCORRECT - misses the case when entire array from start is well-performing
if prefix_sum - 1 in first_occurrence:
max_length = max(max_length, current_index - first_occurrence[prefix_sum - 1])
Why It's Wrong:
When prefix_sum > 0
, the entire interval from index 0 to the current index is well-performing. This is a valid interval that doesn't require looking up previous prefix sums.
The Solution:
Always check if prefix_sum > 0
first:
# CORRECT
if prefix_sum > 0:
max_length = current_index + 1
elif prefix_sum - 1 in first_occurrence:
max_length = max(max_length, current_index - first_occurrence[prefix_sum - 1])
Pitfall 4: Off-by-One Error in Length Calculation
The Mistake: Incorrectly calculating the interval length:
# INCORRECT - missing the +1 max_length = current_index # When prefix_sum > 0 # OR max_length = current_index - first_occurrence[prefix_sum - 1] - 1 # Wrong adjustment
Why It's Wrong:
- When
prefix_sum > 0
, the interval is from index 0 to current_index inclusive, so length =current_index + 1
- When calculating from a previous occurrence, if the previous sum was at index
j
, the valid interval starts atj + 1
and ends atcurrent_index
, giving length =current_index - j
The Solution: Use the correct formulas:
- When
prefix_sum > 0
:max_length = current_index + 1
- When using previous occurrence at index
j
:max_length = current_index - j
In a binary min heap, the minimum element can be found in:
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!