I recently came across an interview question asked by Amazon and I am not able to find an optimized algorithm to solve this question:
You are given an input array wh
You can traverse first from left to right, and calculate the water accumulated for the cases where there is a smaller building on the left and a larger one on the right. You would have to subtract the area of the buildings that are in between these two buildings and are smaller than the left one.
Similar would be the case for right to left.
Here is the code for left to right. I have uploaded this problem on leetcode online judge using this approach.
I find this approach much more intuitive than the standard solution which is present everywhere (calculating the largest building on the right and the left for each i ).
int sum=0, finalAns=0;
idx=0;
while(a[idx]==0 && idx < n)
idx++;
for(int i=idx+1;i<n;i++){
while(a[i] < a[idx] && i<n){
sum += a[i];
i++;
}
if(i==n)
break;
jdx=i;
int area = a[idx] * (jdx-idx-1);
area -= sum;
finalAns += area;
idx=jdx;
sum=0;
}
The time complexity of this approach is O(n), as you are traversing the array two time linearly. Space complexity would be O(1).
The first and the last bars in the list cannot trap water. For the remaining towers, they can trap water when there are max heights to the left and to the right.
water accumulation is: max( min(max_left, max_right) - current_height, 0 )
Iterating from the left, if we know that there is a max_right that is greater, min(max_left, max_right) will become just max_left. Therefore water accumulation is simplified as: max(max_left - current_height, 0) Same pattern when considering from the right side.
From the info above, we can write a O(N) time and O(1) space algorithm as followings(in Python):
def trap_water(A):
water = 0
left, right = 1, len(A)-1
max_left, max_right = A[0], A[len(A)-1]
while left <= right:
if A[left] <= A[right]:
max_left = max(A[left], max_left)
water += max(max_left - A[left], 0)
left += 1
else:
max_right = max(A[right], max_right)
water += max(max_right - A[right], 0)
right -= 1
return water
Refer this website for code, its really plain and simple http://learningarsenal.info/index.php/2015/08/21/amount-of-rain-water-collected-between-towers/
Input: [5,3,7,2,6,4,5,9,1,2] , Output: 14 units
Explanation
Each tower can hold water upto a level of smallest height between heighest tower to left, and highest tower to the right.
Thus we need to calculate highest tower to left on each and every tower, and likewise for the right side.
Here we will be needing two extra arrays for holding height of highest tower to left on any tower say, int leftMax[] and likewise for right side say int rightMax[].
STEP-1
We make a left pass of the given array(i.e int tower[]),and will be maintaining a temporary maximum(say int tempMax) such that on each iteration height of each tower will be compared to tempMax, and if height of current tower is less than tempMax then tempMax will be set as highest tower to left of it, otherwise height of current tower will be assigned as the heighest tower to left and tempMax will be updated with current tower height,
STEP-2
We will be following above procedure only as discussed in STEP-1 to calculate highest tower to right BUT this times making a pass through array from right side.
STEP-3
The amount of water which each tower can hold is-
(minimum height between highest right tower and highest left tower) – (height of tower)
An alternative algorithm in the style of Euclid, which I consider more elegant than all this scanning is:
Set the two tallest towers as the left and right tower. The amount of water contained between these towers is obvious.
Take the next tallest tower and add it. It must be either between the end towers, or not. If it is between the end towers it displaces an amount of water equal to the towers volume (thanks to Archimedes for this hint). If it outside the end towers it becomes a new end tower and the amount of additional water contained is obvious.
Repeat for the next tallest tower until all towers are added.
I've posted code to achieve this (in a modern Euclidean idiom) here: http://www.rosettacode.org/wiki/Water_collected_between_towers#F.23
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
let maxLeftArray = [], maxRightArray = [];
let maxLeft = 0, maxRight = 0;
const ln = height.length;
let trappedWater = 0;
for(let i = 0;i < height.length; i ++) {
maxLeftArray[i] = Math.max(height[i], maxLeft);
maxLeft = maxLeftArray[i];
maxRightArray[ln - i - 1] = Math.max(height[ln - i - 1], maxRight);
maxRight = maxRightArray[ln - i - 1];
}
for(let i = 0;i < height.length; i ++) {
trappedWater += Math.min(maxLeftArray[i], maxRightArray[i]) - height[i];
}
return trappedWater;
};
var arr = [5,3,7,2,6,4,5,9,1,2];
console.log(trap(arr));
You could read the detailed explanation in my blogpost: trapping-rain-water
Tested all the Java solution provided, but none of them passes even half of the test-cases I've come up with, so there is one more Java O(n) solution, with all possible cases covered. The algorithm is really simple:
1) Traverse the input from the beginning, searching for tower that is equal or higher that the given tower, while summing up possible amount of water for lower towers into temporary var.
2) Once the tower found - add that temporary var into main result var and shorten the input list.
3) If no more tower found then reverse the remaining input and calculate again.
public int calculate(List<Integer> input) {
int result = doCalculation(input);
Collections.reverse(input);
result += doCalculation(input);
return result;
}
private static int doCalculation(List<Integer> input) {
List<Integer> copy = new ArrayList<>(input);
int result = 0;
for (ListIterator<Integer> iterator = input.listIterator(); iterator.hasNext(); ) {
final int firstHill = iterator.next();
int tempResult = 0;
int lowerHillsSize = 0;
while (iterator.hasNext()) {
final int nextHill = iterator.next();
if (nextHill >= firstHill) {
iterator.previous();
result += tempResult;
copy = copy.subList(lowerHillsSize + 1, copy.size());
break;
} else {
tempResult += firstHill - nextHill;
lowerHillsSize++;
}
}
}
input.clear();
input.addAll(copy);
return result;
}
For the test cases, please, take a look at this test class.
Feel free to create a pull request if you find uncovered test cases)