Problem
Approach I - Dynamic Programming
-
We calculate how much water each bar can hold.
- Find the leftmost and rightmost boundary of this bar.
- The leftmost boundary is the bar of the least heigh that is greater than the current bar on the left.
- The rightmost boundary is the bar of the least heigh that is greater than the current bar on the right.
-
After finding both boundaries, the water the current bar can hold is
units = min(leftBoundary, rightBoundary) - heightOfCurrentBar
-
In the case that no valid left boundary or right boundary can be found, use the height of itself as the boundary, i.e. the current bar can not hold any water.
How to find the boundaies? Take the leftboundary for example. Let left[i]
be the leftmost boundary of i
th bar.
- Base case:
- The boundary of the first bar is itselt.
left[0] = 0
- The boundary of the first bar is itselt.
- Transition Function.
- If current bar is higher than the leftmost boundary of previous bar, (the current bar is the highest bar so far),
left[i] = i
- Else
left[i] = left[i - 1]
- If current bar is higher than the leftmost boundary of previous bar, (the current bar is the highest bar so far),
The rightmost boundary is calculuated in the similar way, but from right to left.
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
A[i] | 0 | 1 | 0 | 2 | 1 | 0 | 1 | 3 | 2 | 1 | 2 | 1 |
Left | 0 | 1 | 1 | 3 | 3 | 3 | 3 | 7 | 7 | 7 | 7 | 7 |
Right | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 10 | 10 | 10 | 11 |
Unit | 0 | 0 | 1 | 0 | 1 | 2 | 1 | 0 | 0 | 1 | 0 | 0 |
Total = 6.
Pseudo Code
withDP(A)
left = findLeftBoundary(A)
right = findRightBoundary(A)
units = 0
for i = 1 to A.length
units += min(left[i], right[i]) - A[i]
return units
findLeftBoundary(A)
N = A.length
Define an array of length N left
left[0] = 0
for i = 1 to N - 1
if A[i] >= left[i - 1]
left[i] = i
else
left[i] = left[i - 1]
return left
Approach II - Monotonic Stack
In the first approach, we need to iterate the original array three times. With the usage of a stack, we can do it in one pass. The idea is to maintain a monotonically decreasing stack and pop the stack whenever a new bar with greater height is encountered. The sum of the area formed by the bar second to the top, the top, and the new higher bar is the desired result.
withMonotonicStack(A)
N = A.length
sum = 0
Let S be a stack
for i = 0 to N - 1
while S is not empty and A[i] > A[S.top]
top = s.pop
left = s.isEmpty ? top : s.top
right = i
sum += (min(A[left], A[right]) - A[top]) * (right - left - 1)
S.push(i)
return sum
Approach III - Two Pointers
The water a bar can hold is determined by its left maximum height and right maximum height. To be more precisely, it is determined by the smaller value between the left maximum and right maximum height
units = min(leftMax, rightMax) - heightOfCurrentBar
We use two pointers, left
and right
, to track the bars we are going to examine in the next loop. We also keep track of the maximum heights of bar[0 ... left - 1]
(left maximum) and bar[right + 1 ... n - 1]
(right maximum).
Take bar[0]
for example,
# Let A be an array of heights of the bars
withTwoPointers(A)
left = 0, right = A.length - 1
leftMax = 0, rightMax = 11
units = 0
while left < right
if leftMax < rightMax
units += A[leftMax] - A[left]
leftMax = max(leftMax, A[++left])
else
units += rightMax - A[right]
rightMax = max(rightMax, A[--right])
return uints