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
Readable Python Solution:
def water_collected(heights):
water_collected = 0
left_height = []
right_height = []
temp_max = heights[0]
for height in heights:
if (height > temp_max):
temp_max = height
left_height.append(temp_max)
temp_max = heights[-1]
for height in reversed(heights):
if (height > temp_max):
temp_max = height
right_height.insert(0, temp_max)
for i, height in enumerate(heights):
water_collected += min(left_height[i], right_height[i]) - height
return water_collected
You can do this by scanning the array twice.
The first time you scan from top to bottom and store the value of the tallest tower you have yet to encounter when reaching each row.
You then repeat the process, but in reverse. You start from the bottom and work towards the top of the array. You keep track of the tallest tower you have seen so far and compare the height of it to the value for that tower in the other result set.
Take the difference between the lesser of these two values (the shortest of the tallest two towers surrounding the current tower, subtract the height of the tower and add that amount to the total amount of water.
int maxValue = 0;
int total = 0;
int[n] lookAhead
for(i=0;i<n;i++)
{
if(input[i] > maxValue) maxValue = input[i];
lookahead[i] = maxValue;
}
maxValue = 0;
for(i=n-1;i>=0;i--)
{
// If the input is greater than or equal to the max, all water escapes.
if(input[i] >= maxValue)
{
maxValue = input[i];
}
else
{
if(maxValue > lookAhead[i])
{
// Make sure we don't run off the other side.
if(lookAhead[i] > input[i])
{
total += lookAhead[i] - input[i];
}
}
else
{
total += maxValue - input[i];
}
}
}
Here is one more solution written on Scala
def find(a: Array[Int]): Int = {
var count, left, right = 0
while (left < a.length - 1) {
right = a.length - 1
for (j <- a.length - 1 until left by -1) {
if (a(j) > a(right)) right = j
}
if (right - left > 1) {
for (k <- left + 1 until right) count += math.min(a(left), a(right)) - a(k)
left = right
} else left += 1
}
count
}
Once the water's done falling, each position will fill to a level equal to the smaller of the highest tower to the left and the highest tower to the right.
Find, by a rightward scan, the highest tower to the left of each position. Then find, by a leftward scan, the highest tower to the right of each position. Then take the minimum at each position and add them all up.
Something like this ought to work:
int tow[N]; // nonnegative tower heights
int hl[N] = {0}, hr[N] = {0}; // highest-left and highest-right
for (int i = 0; i < n; i++) hl[i] = max(tow[i], (i!=0)?hl[i-1]:0);
for (int i = n-1; i >= 0; i--) hr[i] = max(tow[i],i<(n-1) ? hr[i+1]:0);
int ans = 0;
for (int i = 0; i < n; i++) ans += min(hl[i], hr[i]) - tow[i];
Here is a solution in Groovy in two passes.
assert waterCollected([1, 5, 3, 7, 2]) == 2
assert waterCollected([5, 3, 7, 2, 6, 4, 5, 9, 1, 2]) == 14
assert waterCollected([5, 5, 5, 5]) == 0
assert waterCollected([5, 6, 7, 8]) == 0
assert waterCollected([8, 7, 7, 6]) == 0
assert waterCollected([6, 7, 10, 7, 6]) == 0
def waterCollected(towers) {
int size = towers.size()
if (size < 3) return 0
int left = towers[0]
int right = towers[towers.size() - 1]
def highestToTheLeft = []
def highestToTheRight = [null] * size
for (int i = 1; i < size; i++) {
// Track highest tower to the left
if (towers[i] < left) {
highestToTheLeft[i] = left
} else {
left = towers[i]
}
// Track highest tower to the right
if (towers[size - 1 - i] < right) {
highestToTheRight[size - 1 - i] = right
} else {
right = towers[size - 1 - i]
}
}
int water = 0
for (int i = 0; i < size; i++) {
if (highestToTheLeft[i] && highestToTheRight[i]) {
int minHighest = highestToTheLeft[i] < highestToTheRight[i] ? highestToTheLeft[i] : highestToTheRight[i]
water += minHighest - towers[i]
}
}
return water
}
Here same snippet with an online compiler: https://groovy-playground.appspot.com/#?load=3b1d964bfd66dc623c89
Here's my go at it in Python. Pretty sure it works but haven't tested it.
Two passes through the list (but deleting the list as it finds 'water'):
def answer(heights):
def accWater(lst,sumwater=0):
x,takewater = 1,[]
while x < len(lst):
a,b = lst[x-1],lst[x]
if takewater:
if b < takewater[0]:
takewater.append(b)
x += 1
else:
sumwater += sum(takewater[0]- z for z in takewater)
del lst[:x]
x = 1
takewater = []
else:
if b < a:
takewater.extend([a,b])
x += 1
else:
x += 1
return [lst,sumwater]
heights, swater = accWater(heights)
x, allwater = accWater(heights[::-1],sumwater=swater)
return allwater