Facebook Pixel

3279. Maximum Total Area Occupied by Pistons 🔒

HardArrayHash TableStringCountingPrefix SumSimulation
Leetcode Link

Problem Description

You have a system of pistons that move up and down within a fixed height range. Each piston contributes an area equal to its current position (height from bottom), and you need to find the maximum total area that can be achieved as the pistons move over time.

Given:

  • height: The maximum height any piston can reach (pistons move between 0 and height)
  • positions: An array where positions[i] is the current position of piston i (also the area under that piston)
  • directions: A string where directions[i] is either 'U' (moving up) or 'D' (moving down) for piston i

Movement Rules:

  • Each second, every piston moves 1 unit in its current direction
  • When a piston reaches either boundary (position 0 or position height), it reverses direction

Goal: Find the maximum possible total area under all pistons at any point in time.

For example, if you have 3 pistons at positions [2, 5, 3] with a height limit of 10, the current total area would be 2 + 5 + 3 = 10. As the pistons move according to their directions, this total will change, and you need to find the maximum value it can reach.

The solution uses an event-based approach to track when pistons change direction. It calculates the rate of change of the total area and identifies critical time points where pistons bounce off boundaries. By processing these events in chronological order, it efficiently computes the maximum area without simulating every single time step.

The key insight is that between direction changes, the total area changes linearly. Pistons moving up contribute +1 to the rate of change per second, while pistons moving down contribute -1. When a piston hits a boundary and reverses, it changes the rate by ±2 (from +1 to -1 or vice versa).

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

Intuition

The key observation is that we don't need to simulate the movement second by second. Instead, we can think about this problem in terms of events and rates of change.

At any moment, the total area is changing at a constant rate. Each piston moving up adds +1 per second to the total area, and each piston moving down subtracts 1 per second. So if we have 3 pistons going up and 2 going down, the total area increases by 3 - 2 = 1 per second.

The rate only changes when a piston hits a boundary (at position 0 or height) and reverses direction. When a piston switches from going up to going down, the rate decreases by 2 (it was contributing +1, now it contributes -1). Similarly, when switching from down to up, the rate increases by 2.

We can calculate when each piston will hit its next boundary:

  • A piston at position pos moving up will hit the top after height - pos seconds
  • A piston at position pos moving down will hit the bottom after pos seconds

But pistons don't stop after hitting one boundary - they bounce back and forth. So we need to track all future boundary hits:

  • A piston starting at pos going up hits boundaries at times: height - pos, height * 2 - pos, height * 3 - pos, ...
  • A piston starting at pos going down hits boundaries at times: pos, height + pos, height * 2 + pos, ...

Since we only care about the maximum area, we can limit our search to a reasonable time window. The pattern repeats every 2 * height seconds (time for a complete up-down cycle), so we only need to check the first few bounces.

By collecting all these boundary-hit events, sorting them by time, and processing them in order, we can track how the total area changes over time. At each event, we:

  1. Calculate the area at that moment using the current rate
  2. Update the rate based on which piston(s) are reversing
  3. Keep track of the maximum area seen

This approach runs in O(n log n) time due to sorting, where n is the number of events, which is much more efficient than simulating every second.

Learn more about Prefix Sum patterns.

Solution Approach

The implementation uses an event-based simulation with a dictionary to track when rate changes occur.

Data Structures:

  • delta: A dictionary that maps time points to rate changes. When a piston bounces at time t, we store how the rate of change will be modified at that time.
  • diff: The current rate of change (how much the total area changes per second)
  • res: The current total area at any given time point
  • ans: The maximum area encountered

Initial Setup:

  1. Calculate the initial total area by summing all positions: res = sum(positions)
  2. Calculate the initial rate of change diff:
    • For each piston going up ('U'): add 1 to diff
    • For each piston going down ('D'): subtract 1 from diff

Recording Future Events: For each piston, we record when it will hit boundaries and reverse direction:

  • For pistons moving UP (from position pos):

    • First boundary hit at time height - pos (reaching the top)
    • When it bounces, it changes from +1 to -1 contribution, so rate changes by -2
    • Next boundary hit at time height * 2 - pos (reaching bottom after bouncing)
    • When it bounces again, it changes from -1 to +1, so rate changes by +2
  • For pistons moving DOWN (from position pos):

    • First boundary hit at time pos (reaching the bottom)
    • When it bounces, it changes from -1 to +1 contribution, so rate changes by +2
    • Next boundary hit at time height + pos (reaching top after bouncing)
    • When it bounces again, it changes from +1 to -1, so rate changes by -2

Processing Events:

  1. Sort all events by time using sorted(delta.items())
  2. For each event at time cur:
    • Calculate the area at this time: res += (cur - pre) * diff
      • This adds the area accumulated since the last event
    • Update the maximum: ans = max(ans, res)
    • Apply the rate change for this event: diff += d
    • Update pre = cur for the next iteration

Example Walkthrough: If we have a piston at position 3 moving up with height = 10:

  • Initial contribution to area: 3
  • Initial contribution to rate: +1
  • At time 7 (= 10 - 3), it hits the top and reverses
  • Rate change at time 7: -2 (from +1 to -1)
  • At time 17 (= 10 * 2 - 3), it hits the bottom again
  • Rate change at time 17: +2 (from -1 to +1)

The algorithm efficiently finds the maximum by only checking these critical time points where the rate changes, avoiding the need to simulate every single second.

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 a small example with height = 6, positions = [1, 5], and `directions = "UD".

Initial State:

  • Piston 0: at position 1, moving UP ('U')
  • Piston 1: at position 5, moving DOWN ('D')
  • Initial total area: 1 + 5 = 6
  • Initial rate of change: +1 (for UP) - 1 (for DOWN) = 0

Recording Events:

For Piston 0 (position 1, moving UP):

  • Hits top at time 5 (= 6 - 1)
    • Rate change: -2 (switches from +1 to -1)
  • Hits bottom at time 11 (= 6 × 2 - 1)
    • Rate change: +2 (switches from -1 to +1)

For Piston 1 (position 5, moving DOWN):

  • Hits bottom at time 5 (= 5)
    • Rate change: +2 (switches from -1 to +1)
  • Hits top at time 11 (= 6 + 5)
    • Rate change: -2 (switches from +1 to -1)

Event Timeline:

Time 0: area = 6, rate = 0
Time 5: Two events occur
  - Both pistons reverse
  - Total rate change: -2 + 2 = 0
  - Area at time 5: 6 + (5 - 0) × 0 = 6
  - New rate: 0 + 0 = 0
Time 11: Two events occur
  - Both pistons reverse again
  - Total rate change: +2 - 2 = 0
  - Area at time 11: 6 + (11 - 5) × 0 = 6
  - New rate: 0 + 0 = 0

In this example, the rate stays at 0 throughout, so the area remains constant at 6. The maximum area is 6.

A More Interesting Example:

Let's try height = 5, positions = [2, 4, 1], and `directions = "UUD".

Initial State:

  • Total area: 2 + 4 + 1 = 7
  • Rate: +1 + 1 - 1 = +1 (two UP, one DOWN)

Recording Events:

Piston 0 (position 2, UP):

  • Hits top at time 3 (= 5 - 2), rate change: -2

Piston 1 (position 4, UP):

  • Hits top at time 1 (= 5 - 4), rate change: -2

Piston 2 (position 1, DOWN):

  • Hits bottom at time 1 (= 1), rate change: +2

Processing Events in Order:

Time 0: area = 7, rate = +1
Time 1: Two events (Piston 1 hits top, Piston 2 hits bottom)
  - Area at time 1: 7 + (1 - 0) × 1 = 8
  - Rate change: -2 + 2 = 0
  - New rate: 1 + 0 = 1
Time 3: One event (Piston 0 hits top)
  - Area at time 3: 8 + (3 - 1) × 1 = 10
  - Rate change: -2
  - New rate: 1 - 2 = -1

After time 3, the area starts decreasing (rate = -1). The maximum area achieved is 10 at time 3.

Solution Implementation

1from collections import defaultdict
2from typing import List
3
4class Solution:
5    def maxArea(self, height: int, positions: List[int], directions: str) -> int:
6        # Dictionary to track when the rate of change (slope) changes
7        slope_changes = defaultdict(int)
8      
9        # Current slope (rate of change of total distance)
10        current_slope = 0
11      
12        # Initial total distance (sum of all positions)
13        total_distance = 0
14      
15        # Process each object's position and direction
16        for position, direction in zip(positions, directions):
17            total_distance += position
18          
19            if direction == "U":  # Moving upward
20                # Slope increases by 1 (moving away from bottom)
21                current_slope += 1
22                # At time (height - position), object hits top and reverses
23                slope_changes[height - position] -= 2
24                # At time (2 * height - position), object returns to bottom
25                slope_changes[height * 2 - position] += 2
26            else:  # Moving downward (direction == "D")
27                # Slope decreases by 1 (moving toward bottom)
28                current_slope -= 1
29                # At time position, object hits bottom and reverses
30                slope_changes[position] += 2
31                # At time (height + position), object returns to top
32                slope_changes[height + position] -= 2
33      
34        # Track maximum area encountered
35        max_area = total_distance
36      
37        # Previous time point
38        previous_time = 0
39      
40        # Process each time point where slope changes occur
41        for current_time, slope_delta in sorted(slope_changes.items()):
42            # Update total distance based on elapsed time and current slope
43            total_distance += (current_time - previous_time) * current_slope
44            previous_time = current_time
45          
46            # Update the slope for the next interval
47            current_slope += slope_delta
48          
49            # Update maximum area if current is larger
50            max_area = max(max_area, total_distance)
51      
52        return max_area
53
1class Solution {
2    public long maxArea(int height, int[] positions, String directions) {
3        // TreeMap to store time points where direction changes occur
4        // Key: time point, Value: change in slope at that time
5        Map<Integer, Integer> slopeChanges = new TreeMap<>();
6      
7        // Current slope (rate of change of total distance)
8        int currentSlope = 0;
9      
10        // Initial sum of all positions
11        long currentTotalDistance = 0;
12      
13        // Calculate initial state and set up slope change events
14        for (int i = 0; i < positions.length; i++) {
15            int position = positions[i];
16            char direction = directions.charAt(i);
17          
18            // Add initial position to total
19            currentTotalDistance += position;
20          
21            if (direction == 'U') {
22                // Particle moving upward
23                currentSlope++;  // Initially contributes +1 to slope
24              
25                // At time (height - position), particle hits top and reverses
26                slopeChanges.merge(height - position, -2, Integer::sum);
27              
28                // At time (2 * height - position), particle returns to bottom and reverses again
29                slopeChanges.merge(height * 2 - position, 2, Integer::sum);
30            } else {
31                // Particle moving downward
32                currentSlope--;  // Initially contributes -1 to slope
33              
34                // At time position, particle hits bottom and reverses
35                slopeChanges.merge(position, 2, Integer::sum);
36              
37                // At time (height + position), particle hits top and reverses again
38                slopeChanges.merge(height + position, -2, Integer::sum);
39            }
40        }
41      
42        // Track maximum total distance
43        long maxTotalDistance = currentTotalDistance;
44      
45        // Previous time point for calculating intervals
46        int previousTime = 0;
47      
48        // Process each time point where slope changes
49        for (Map.Entry<Integer, Integer> entry : slopeChanges.entrySet()) {
50            int currentTime = entry.getKey();
51            int slopeChange = entry.getValue();
52          
53            // Update total distance for the time interval
54            // Distance changes by (time elapsed) * (current slope)
55            currentTotalDistance += (long) (currentTime - previousTime) * currentSlope;
56          
57            // Update previous time for next iteration
58            previousTime = currentTime;
59          
60            // Apply slope change at this time point
61            currentSlope += slopeChange;
62          
63            // Update maximum if current total is larger
64            maxTotalDistance = Math.max(maxTotalDistance, currentTotalDistance);
65        }
66      
67        return maxTotalDistance;
68    }
69}
70
1class Solution {
2public:
3    long long maxArea(int height, vector<int>& positions, string directions) {
4        // Map to store time points where velocity changes occur
5        // Key: time point, Value: velocity change at that time
6        map<int, int> velocityChanges;
7      
8        // Current total velocity (sum of all object velocities)
9        int currentVelocity = 0;
10      
11        // Current total distance covered by all objects
12        long long currentTotalDistance = 0;
13
14        // Process each object's initial position and direction
15        for (int i = 0; i < positions.size(); ++i) {
16            int startPosition = positions[i];
17            char movementDirection = directions[i];
18          
19            // Add initial position to total distance
20            currentTotalDistance += startPosition;
21
22            if (movementDirection == 'U') {
23                // Object moving upward (velocity = +1)
24                ++currentVelocity;
25              
26                // At time (height - startPosition), object hits top and reverses
27                velocityChanges[height - startPosition] -= 2;  // Velocity changes from +1 to -1
28              
29                // At time (2*height - startPosition), object returns to bottom and reverses again
30                velocityChanges[height * 2 - startPosition] += 2;  // Velocity changes from -1 to +1
31            } else {
32                // Object moving downward (velocity = -1)
33                --currentVelocity;
34              
35                // At time startPosition, object hits bottom and reverses
36                velocityChanges[startPosition] += 2;  // Velocity changes from -1 to +1
37              
38                // At time (height + startPosition), object hits top and reverses again
39                velocityChanges[height + startPosition] -= 2;  // Velocity changes from +1 to -1
40            }
41        }
42
43        // Track the maximum total distance achieved
44        long long maxTotalDistance = currentTotalDistance;
45      
46        // Previous time point for calculating distance increments
47        int previousTime = 0;
48
49        // Process each time point where velocity changes
50        for (const auto& [currentTime, velocityDelta] : velocityChanges) {
51            // Calculate distance covered in the time interval
52            long long distanceIncrement = static_cast<long long>(currentTime - previousTime) * currentVelocity;
53            currentTotalDistance += distanceIncrement;
54          
55            // Update previous time point
56            previousTime = currentTime;
57          
58            // Apply velocity change at current time
59            currentVelocity += velocityDelta;
60          
61            // Update maximum distance if current is greater
62            maxTotalDistance = max(maxTotalDistance, currentTotalDistance);
63        }
64
65        return maxTotalDistance;
66    }
67};
68
1/**
2 * Calculates the maximum area formed by vertical lines at different positions
3 * @param height - The height of the vertical lines
4 * @param positions - Array of initial positions of the vertical lines
5 * @param directions - String indicating movement direction for each line ('L' for left, 'R' for right)
6 * @returns The maximum area that can be formed between any two lines
7 */
8function maxArea(height: number, positions: number[], directions: string): number {
9    // Number of vertical lines
10    const n = positions.length;
11  
12    // If less than 2 lines, no area can be formed
13    if (n < 2) {
14        return 0;
15    }
16  
17    // Track maximum area found
18    let maxAreaValue = 0;
19  
20    // Create array of line objects with position and direction
21    const lines: Array<{position: number, direction: string}> = [];
22    for (let i = 0; i < n; i++) {
23        lines.push({
24            position: positions[i],
25            direction: directions[i]
26        });
27    }
28  
29    // Sort lines by initial position for easier processing
30    lines.sort((a, b) => a.position - b.position);
31  
32    // Calculate initial maximum area
33    for (let i = 0; i < n; i++) {
34        for (let j = i + 1; j < n; j++) {
35            const width = Math.abs(lines[j].position - lines[i].position);
36            const currentArea = width * height;
37            maxAreaValue = Math.max(maxAreaValue, currentArea);
38        }
39    }
40  
41    // Simulate movement over time to find maximum possible area
42    const maxTime = 200; // Reasonable upper bound for simulation
43  
44    for (let time = 1; time <= maxTime; time++) {
45        // Update positions based on direction
46        for (let i = 0; i < n; i++) {
47            if (lines[i].direction === 'L') {
48                lines[i].position -= 1;
49            } else if (lines[i].direction === 'R') {
50                lines[i].position += 1;
51            }
52        }
53      
54        // Calculate maximum area at current time
55        for (let i = 0; i < n; i++) {
56            for (let j = i + 1; j < n; j++) {
57                const width = Math.abs(lines[j].position - lines[i].position);
58                const currentArea = width * height;
59                maxAreaValue = Math.max(maxAreaValue, currentArea);
60            }
61        }
62    }
63  
64    return maxAreaValue;
65}
66

Time and Space Complexity

Time Complexity: O(n log n) where n is the number of positions/directions.

  • Creating the delta dictionary and calculating initial values takes O(n) time as we iterate through all positions once
  • The sorted(delta.items()) operation dominates the time complexity, requiring O(m log m) where m is the number of unique keys in delta
  • Since each position can contribute at most 2 entries to delta (one addition and one subtraction), we have m ≤ 2n, making this O(n log n)
  • The final iteration through sorted delta items takes O(m) = O(n) time
  • Overall: O(n) + O(n log n) + O(n) = O(n log n)

Space Complexity: O(n)

  • The delta dictionary stores at most 2n key-value pairs (2 entries per position), requiring O(n) space
  • The sorted operation creates a list of delta items which also takes O(n) space
  • Other variables (diff, res, ans, pre) use O(1) space
  • Overall: O(n) + O(n) + O(1) = O(n)

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

Common Pitfalls

1. Integer Overflow in Time Calculations

When calculating bounce times like height * 2 - position, these values can become very large if height is large. In languages with fixed integer sizes, this could cause overflow issues.

Solution: In Python, integers have arbitrary precision so this isn't an issue, but in other languages, use long/64-bit integers or check for potential overflow conditions.

2. Missing Edge Cases for Initial Positions

A common mistake is not handling pistons that start at the boundaries (position 0 or position height). The code assumes all pistons are strictly between boundaries initially.

Solution: Add validation to handle boundary cases:

for position, direction in zip(positions, directions):
    if position == 0 and direction == "D":
        # Piston at bottom trying to go down - should immediately reverse
        direction = "U"
    elif position == height and direction == "U":
        # Piston at top trying to go up - should immediately reverse
        direction = "D"

3. Incorrect Handling of Simultaneous Events

Multiple pistons might hit boundaries at the exact same time. Using a simple dictionary correctly aggregates these changes, but developers might mistakenly overwrite values instead of accumulating them.

Wrong approach:

slope_changes[time] = -2  # This overwrites any existing value

Correct approach (as shown in solution):

slope_changes[time] -= 2  # This accumulates with existing changes

4. Not Checking Maximum at Time Zero

The code checks maximum area after processing each event, but if all pistons are moving down initially, the maximum might be at time 0 before any movement occurs.

Solution: Initialize max_area with the initial total distance (which the code already does correctly).

5. Forgetting to Update Previous Time

A subtle bug occurs if you forget to update previous_time after processing each event, causing incorrect area calculations.

Solution: Always update previous_time = current_time after processing each event (as shown in the solution).

6. Misunderstanding the Physical Model

Developers might incorrectly think that pistons stop at boundaries or that the area contribution becomes 0 at position 0. The problem states pistons bounce and continue moving, and even at position 0, the area contribution is 0 (not negative).

Solution: Carefully read the problem statement and understand that pistons continuously bounce between 0 and height, never stopping.

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

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

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

Load More