3279. Maximum Total Area Occupied by Pistons 🔒
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 andheight
)positions
: An array wherepositions[i]
is the current position of pistoni
(also the area under that piston)directions
: A string wheredirections[i]
is either'U'
(moving up) or'D'
(moving down) for pistoni
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).
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 afterheight - pos
seconds - A piston at position
pos
moving down will hit the bottom afterpos
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:
- Calculate the area at that moment using the current rate
- Update the rate based on which piston(s) are reversing
- 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 timet
, 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 pointans
: The maximum area encountered
Initial Setup:
- Calculate the initial total area by summing all positions:
res = sum(positions)
- Calculate the initial rate of change
diff
:- For each piston going up (
'U'
): add1
todiff
- For each piston going down (
'D'
): subtract1
fromdiff
- For each piston going up (
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
- First boundary hit at time
-
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
- First boundary hit at time
Processing Events:
- Sort all events by time using
sorted(delta.items())
- 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
- Calculate the area at this time:
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 EvaluatorExample 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, requiringO(m log m)
wherem
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 thisO(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), requiringO(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.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
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
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!