Facebook Pixel

3638. Maximum Balanced Shipments

Problem Description

You are given an integer array weight of length n, representing the weights of n parcels arranged in a straight line.

A shipment is defined as a contiguous subarray of parcels. A shipment is considered balanced if the weight of the last parcel in that shipment is strictly less than the maximum weight among all parcels in that shipment.

Your task is to select a set of non-overlapping, contiguous, balanced shipments such that each parcel appears in at most one shipment (some parcels may remain unshipped).

Return the maximum possible number of balanced shipments that can be formed.

For example, if we have parcels with weights [3, 5, 2, 7, 4]:

  • The subarray [3, 5, 2] is a balanced shipment because the last parcel (2) is less than the maximum weight (5)
  • The subarray [7, 4] is a balanced shipment because the last parcel (4) is less than the maximum weight (7)
  • These two shipments don't overlap, giving us 2 balanced shipments total

The greedy approach works by traversing the array and keeping track of the maximum weight seen so far. Whenever we find a parcel whose weight is less than the current maximum, we can end a balanced shipment there (using that parcel as the last one), increment our count, and start looking for the next shipment.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we want to maximize the number of balanced shipments, which means we should try to create shipments as soon as possible rather than making them longer.

Think about it this way: when we traverse the array and keep track of the maximum weight seen so far, whenever we encounter a parcel that is lighter than this maximum, we have an opportunity to form a balanced shipment. Why should we take this opportunity immediately?

Consider what happens if we don't end the shipment here and continue:

  • We might encounter an even heavier parcel later, which would update our maximum
  • The next opportunity to end a shipment would require finding a parcel lighter than this new, potentially higher maximum
  • This makes it harder to form balanced shipments

By greedily ending a shipment as soon as we find a valid ending parcel (one that's lighter than the current maximum), we:

  1. Secure one balanced shipment immediately
  2. Reset our tracking, allowing us to start fresh for the next potential shipment
  3. Avoid the risk of making it harder to form future shipments by accumulating higher maximum values

For example, with weights [3, 5, 2, 7, 4]:

  • We see 3, then 5 (max becomes 5)
  • We see 2, which is less than 5 - we can form a balanced shipment [3, 5, 2]
  • If we had continued without ending here, we'd see 7 next, making our max 7, and then 4 would still give us one shipment but we'd have missed the opportunity to form two

This greedy strategy of "take the first valid opportunity" ensures we maximize the total number of balanced shipments we can form.

Learn more about Stack, Greedy, Dynamic Programming and Monotonic Stack patterns.

Solution Approach

The implementation uses a greedy algorithm with a simple linear scan through the array. Here's how it works:

We maintain two variables:

  • ans: counts the number of balanced shipments formed
  • mx: tracks the maximum weight in the current potential shipment

The algorithm processes each parcel weight x in the array sequentially:

  1. Update the maximum: For each element x, we update mx = max(mx, x) to maintain the maximum weight seen in the current segment.

  2. Check for balanced shipment opportunity: If x < mx, this means the current parcel can serve as the last parcel of a balanced shipment (since it's strictly less than the maximum weight in the segment).

  3. Form a shipment and reset: When we find such an opportunity:

    • Increment ans by 1 (we've formed one balanced shipment)
    • Reset mx to 0 to start tracking a new potential shipment

Here's the step-by-step execution with an example weight = [3, 5, 2, 7, 4]:

  • x = 3: mx = max(0, 3) = 3, not less than mx, continue
  • x = 5: mx = max(3, 5) = 5, not less than mx, continue
  • x = 2: mx = 5, and 2 < 5, so we form a shipment, ans = 1, reset mx = 0
  • x = 7: mx = max(0, 7) = 7, not less than mx, continue
  • x = 4: mx = 7, and 4 < 7, so we form a shipment, ans = 2, reset mx = 0

The time complexity is O(n) since we make a single pass through the array, and the space complexity is O(1) as we only use two variables regardless of input size.

This greedy approach is optimal because forming a shipment as soon as possible never prevents us from forming future shipments - it only creates more opportunities by resetting our tracking.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with a simple example: weight = [4, 6, 3, 8, 5, 2]

We'll track two variables:

  • ans = 0 (number of balanced shipments formed)
  • mx = 0 (maximum weight in current segment)

Step 1: Process weight 4

  • Update mx = max(0, 4) = 4
  • Is 4 < 4? No, so continue
  • Current segment: [4]

Step 2: Process weight 6

  • Update mx = max(4, 6) = 6
  • Is 6 < 6? No, so continue
  • Current segment: [4, 6]

Step 3: Process weight 3

  • mx is still 6
  • Is 3 < 6? Yes! We can form a balanced shipment
  • The shipment [4, 6, 3] is balanced (last element 3 < max element 6)
  • Increment ans = 1 and reset mx = 0

Step 4: Process weight 8

  • Update mx = max(0, 8) = 8
  • Is 8 < 8? No, so continue
  • Current segment: [8]

Step 5: Process weight 5

  • mx is still 8
  • Is 5 < 8? Yes! We can form another balanced shipment
  • The shipment [8, 5] is balanced (last element 5 < max element 8)
  • Increment ans = 2 and reset mx = 0

Step 6: Process weight 2

  • Update mx = max(0, 2) = 2
  • Is 2 < 2? No, so continue
  • We've reached the end

Result: We formed 2 balanced shipments: [4, 6, 3] and [8, 5]. The parcel with weight 2 remains unshipped, which is allowed by the problem.

Solution Implementation

1from typing import List
2
3class Solution:
4    def maxBalancedShipments(self, weight: List[int]) -> int:
5        # Initialize counter for balanced shipments and track maximum weight
6        balanced_count = 0
7        current_max = 0
8      
9        # Process each weight in the list
10        for current_weight in weight:
11            # Update the maximum weight seen so far
12            current_max = max(current_max, current_weight)
13          
14            # If current weight is less than the maximum seen,
15            # we can form a balanced shipment
16            if current_weight < current_max:
17                balanced_count += 1
18                # Reset maximum for the next potential shipment
19                current_max = 0
20      
21        return balanced_count
22
1class Solution {
2    public int maxBalancedShipments(int[] weight) {
3        // Initialize counter for balanced shipments
4        int balancedShipmentsCount = 0;
5      
6        // Track the maximum weight seen in the current segment
7        int currentMaxWeight = 0;
8      
9        // Iterate through each weight in the array
10        for (int currentWeight : weight) {
11            // Update the maximum weight if current weight is larger
12            currentMaxWeight = Math.max(currentMaxWeight, currentWeight);
13          
14            // If current weight is less than the maximum weight seen so far,
15            // we've found a valid balanced shipment point
16            if (currentWeight < currentMaxWeight) {
17                // Increment the count of balanced shipments
18                balancedShipmentsCount++;
19              
20                // Reset the maximum weight for the next segment
21                currentMaxWeight = 0;
22            }
23        }
24      
25        // Return the total number of balanced shipments found
26        return balancedShipmentsCount;
27    }
28}
29
1class Solution {
2public:
3    int maxBalancedShipments(vector<int>& weight) {
4        int result = 0;           // Counter for the number of balanced shipments
5        int maxWeight = 0;        // Track the maximum weight in the current segment
6      
7        // Iterate through each weight in the array
8        for (int currentWeight : weight) {
9            // Update the maximum weight seen so far in this segment
10            maxWeight = max(maxWeight, currentWeight);
11          
12            // If current weight is less than the maximum weight seen,
13            // we've found a decreasing point, marking end of a balanced shipment
14            if (currentWeight < maxWeight) {
15                ++result;         // Increment the count of balanced shipments
16                maxWeight = 0;    // Reset maximum weight for the next segment
17            }
18        }
19      
20        return result;
21    }
22};
23
1/**
2 * Calculates the maximum number of balanced shipments that can be created
3 * by partitioning the weight array into segments where each segment ends
4 * when we encounter a weight less than the maximum weight seen so far in that segment
5 * 
6 * @param weight - Array of weights to be partitioned into shipments
7 * @returns The maximum number of balanced shipments
8 */
9function maxBalancedShipments(weight: number[]): number {
10    // Initialize the count of shipments and the current maximum weight
11    let shipmentCount: number = 0;
12    let currentMaxWeight: number = 0;
13  
14    // Iterate through each weight in the array
15    for (const currentWeight of weight) {
16        // Update the maximum weight seen in the current segment
17        currentMaxWeight = Math.max(currentMaxWeight, currentWeight);
18      
19        // If current weight is less than the maximum weight in this segment,
20        // we can end the current shipment and start a new one
21        if (currentWeight < currentMaxWeight) {
22            shipmentCount++;
23            // Reset the maximum weight for the next segment
24            currentMaxWeight = 0;
25        }
26    }
27  
28    return shipmentCount;
29}
30

Time and Space Complexity

The time complexity is O(n), where n is the length of the array weight. The algorithm iterates through the array exactly once with a single for loop, performing constant-time operations (comparisons, assignments, and max function) at each iteration.

The space complexity is O(1), using only constant extra space. The algorithm only uses a fixed number of variables (ans, mx, and x) regardless of the input size, with no additional data structures that grow with the input.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding the Greedy Reset Logic

A common mistake is thinking that resetting current_max to 0 after forming a shipment might miss optimal solutions. Developers might worry: "What if I could form a longer balanced shipment by not greedily taking the first opportunity?"

Why this concern is unfounded: The greedy approach is actually optimal here. Once we find a valid ending point for a balanced shipment (where current_weight < current_max), extending this shipment further can never increase our total count - at best, we get the same single shipment but longer. By taking the shipment immediately, we maximize opportunities for future shipments.

Pitfall 2: Incorrect Initialization of Maximum

Some might initialize current_max to the first element of the array or to float('-inf'), thinking they need a proper starting comparison value.

Issue with wrong initialization:

  • If initialized to weight[0], you'd need special handling for the first element
  • If initialized to float('-inf'), every single-element segment would incorrectly be considered balanced

Solution: Initialize current_max to 0. This works because:

  • All weights are positive integers (based on the problem context)
  • The first element will properly set current_max on the first iteration
  • It naturally handles empty arrays

Pitfall 3: Off-by-One Errors in Shipment Formation

Developers might accidentally form a shipment that includes the current element as part of the next shipment rather than ending the current shipment with it.

Incorrect approach:

if current_weight < current_max:
    balanced_count += 1
    current_max = current_weight  # Wrong! This includes current element in next shipment

Correct approach:

if current_weight < current_max:
    balanced_count += 1
    current_max = 0  # Correct! Start fresh for next shipment

The current element that satisfies the condition is the last element of the current shipment, not the first element of the next one.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More