Longest Well-Performing Interval

MEDIUM

Description

We are given hours, a list of the number of hours worked per day for a given employee.

A day is considered to be a tiring day if and only if the number of hours worked is (strictly) greater than 8.

A well-performing interval is an interval of days for which the number of tiring days is strictly larger than the number of non-tiring days.

Return the length of the longest well-performing interval.

 

Example 1:

Input: hours = [9,9,6,0,6,6,9]
Output: 3
Explanation: The longest well-performing interval is [9,9,6].

Example 2:

Input: hours = [6,6,6]
Output: 0

 

Constraints:

  • 1 <= hours.length <= 104
  • 0 <= hours[i] <= 16

Approaches

Checkout 2 different approaches to solve Longest Well-Performing Interval. Click on different approaches to view the approach and algorithm in detail.

Prefix Sum with HashMap

A more efficient solution uses the concept of prefix sums combined with a HashMap. As before, we convert tiring days to +1 and non-tiring days to -1. We then compute the prefix sum as we iterate through this new score array.

The problem is to find indices i and j (i <= j) that maximize j - i + 1, such that the sum of scores from i to j is positive. This sum can be expressed as prefixSum[j] - prefixSum[i-1] > 0, or prefixSum[j] > prefixSum[i-1].

We can iterate through the array with index j, calculating the current_sum (which is prefixSum[j]). For each j, we need to find the earliest index i-1 where the prefix sum was smaller than current_sum. A HashMap is used to store the first index at which each prefix sum value appeared. This allows us to find the required i-1 efficiently.

Algorithm

  1. Transform the problem: represent tiring days as +1 and non-tiring days as -1. The goal is to find the longest subarray with a sum > 0.
  2. This problem can be solved in a single pass using a HashMap and the concept of prefix sums.
  3. Initialize sum = 0, maxLength = 0, and a HashMap map to store the first-seen index of each prefix sum. Add a base case map.put(0, -1) to handle intervals starting from index 0.
  4. Iterate through the hours array from i = 0 to n-1: a. Update the running sum: sum += (hours[i] > 8) ? 1 : -1. b. If sum is not already in the map, store its first occurrence: map.put(sum, i). c. Check if the current prefix sum sum is positive. If sum > 0, it means the interval from the beginning [0, i] is well-performing. Its length is i + 1. Update maxLength = max(maxLength, i + 1). d. If sum <= 0, we look for a previous prefix sum prev_sum such that sum - prev_sum > 0, which simplifies to prev_sum < sum. To get the longest such interval ending at i, we need the earliest (smallest index) prev_sum. The best candidate for this is sum - 1, as it represents the smallest possible positive difference (1). We check if map.containsKey(sum - 1). If it exists, it means we found an interval [map.get(sum - 1) + 1, i] with a sum of 1. We update maxLength = max(maxLength, i - map.get(sum - 1)).
  5. Return maxLength.

We iterate through the array once, maintaining a running sum. We use a HashMap to keep track of the first time we encounter each sum value. The key is the sum, and the value is the index.

We initialize the map with {0: -1} to correctly handle cases where a well-performing interval starts at index 0.

For each day i, we update our sum.

  • If sum > 0, the entire interval from the start [0, i] is well-performing. This has length i+1, which is the longest possible interval ending at i. We update our max length accordingly.
  • If sum <= 0, a well-performing interval ending at i must start at some k > 0. We are looking for a previous prefix sum, prev_sum, that is less than the current sum. To maximize the interval length i - k, we need the smallest k. The condition sum - prev_sum > 0 is equivalent to prev_sum <= sum - 1. The key insight is that we only need to check for the existence of sum - 1 in our map. If map.containsKey(sum - 1), we have found an interval with a sum of at least 1, and map.get(sum - 1) gives us the end index of the prefix that we subtract. The length of this interval is i - map.get(sum - 1). We update our max length with this value if it's larger.

We only add a sum to the map if it's not already there. This ensures we always have the earliest index for any given sum, which is crucial for finding the longest interval.

import java.util.HashMap;
import java.util.Map;

class Solution {
    public int longestWPI(int[] hours) {
        int maxLength = 0;
        int sum = 0;
        // Map to store the first occurrence of a prefix sum
        Map<Integer, Integer> map = new HashMap<>();
        // Base case for intervals starting at index 0
        map.put(0, -1);

        for (int i = 0; i < hours.length; i++) {
            sum += (hours[i] > 8) ? 1 : -1;

            // If the current sum is positive, the interval from the beginning is well-performing.
            // This is the longest possible interval ending at i.
            if (sum > 0) {
                maxLength = i + 1;
            } else {
                // If sum <= 0, we look for a previous prefix sum `sum - 1`.
                // This guarantees an interval sum of `sum - (sum - 1) = 1 > 0`.
                // We check for `sum - 1` because it gives the longest possible interval
                // for a subarray ending at `i` with a positive sum.
                if (map.containsKey(sum - 1)) {
                    maxLength = Math.max(maxLength, i - map.get(sum - 1));
                }
            }
            
            // Store the first time we see a particular sum.
            // If the sum is already in the map, we don't update it because we want the earliest index.
            map.putIfAbsent(sum, i);
        }

        return maxLength;
    }
}

Complexity Analysis

Time Complexity: O(n), where n is the number of days. We iterate through the array once, and HashMap operations (put, get) take average O(1) time.Space Complexity: O(n) in the worst case, as the HashMap could potentially store a distinct prefix sum for each element. The range of possible sums is from `-n` to `n`.

Pros and Cons

Pros:
  • Highly efficient with linear time complexity.
  • Solves the problem in a single pass through the input array.
Cons:
  • The logic can be less intuitive to derive compared to the brute-force approach.
  • Requires extra space for the HashMap.

Code Solutions

Checking out 3 solutions in different languages for Longest Well-Performing Interval. Click on different languages to view the code.

class Solution { public int longestWPI ( int [] hours ) { int ans = 0 , s = 0 ; Map < Integer , Integer > pos = new HashMap <>(); for ( int i = 0 ; i < hours . length ; ++ i ) { s += hours [ i ] > 8 ? 1 : - 1 ; if ( s > 0 ) { ans = i + 1 ; } else if ( pos . containsKey ( s - 1 )) { ans = Math . max ( ans , i - pos . get ( s - 1 )); } pos . putIfAbsent ( s , i ); } return ans ; } }

Video Solution

Watch the video walkthrough for Longest Well-Performing Interval



Patterns:

Prefix Sum

Data Structures:

ArrayHash TableStackMonotonic Stack

Companies:

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.

Longest Well-Performing Interval | Scale Engineer