R

E

A

C

T

O

xamples 

epeat

ode

pproach

ptimize

est

{Rainwater Collector}

The Question

You're an industrious programmer that lives off the grid. The local well that you use to fetch water has gone dry, so you've decided to collect rain water to filter. However, your collection device isn't flat. Determine how much rain you could possibly collect at once.

 

PROBLEM: Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water your collection device is able to trap after raining.

Example

EXAMPLE: Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

Keep in mind...

  • Water will only gather up to the height of its reservoir walls.

 

  • We can therefore think about this problem in terms of height.

  • Water cannot be held in the first or last bar, due to no containing wall on one side.

The Approach

  • Instead of summing the volume vertically, we can think about how much volume exists in a horizontal plane at each incremental height.

  • Our solution goes about this by

    • starting at the highest 'peak',

    • summing the total amount of volume at that level,

    • decrementing the height by 1 and

    • repeating the process until a height of 0 is reached.

Tests to Run

// vol = 7
var a = [0,0,1,2,4,3,2,5,0,0,2,1]
console.log('collection device "a" can hold', totalVol(a))
 
// vol = 6
var b = [0,1,0,2,1,0,1,3,2,1,2,1]
console.log('collection device "b" can hold', totalVol(b))
 
// vol = 12
var c =[0,3,0,1,0,0,0,1,0,2]
console.log('collection device "c" can hold', totalVol(c))
 
// vol = 8
var d = [0,1,0,3,5,0,0,0,2,0,1]
console.log('collection device "d" can hold', totalVol(d))
 
// vol = 38
var e = [0,5,3,2,8,8,1,1,2,4,3,3,7,1,2,4,3,2]
console.log('collection device "e" can hold', totalVol(e))
'use strict'

function rainCollector(arr){
    var volume = 0;
    var maxVal = 0;
    
    arr.forEach(function(elem){
        if(elem > maxVal) maxVal = elem;
    })
    //look at horizontal layers to determine volume
    for(let height = maxVal; height>0; height--){
        let positions = [];
        //find indices of 'peaks' at each height 
        for(let j = 0; j < arr.length; j++){
            if(arr[j] >= height) positions.push(j);
        }
        //find the volume at each layer 
        volume = positions.reduce(function(accVol, curr, index){
            //the current peak index minus the previous peak index - 1 
            //e.g. index 3 - index 0 - 1 gives a volume of 2 as expected
            if(index > 0) return accVol + (curr - positions[index - 1] - 1);
            return acc;
        }, volume)
    }
    return volume;
}

var rainArr = [0,5,3,2,8,8,1,1,2,4,3,3,7,1,2,4,3,2];
console.log(rainCollector(rainArr)); //logs 38

Big O Notation

function rainCollector(arr){ 
    var volume = 0; //space O(1)
    var maxVal = 0; //space O(1)
    
    arr.forEach(function(elem){ //time O(n) - 'n' being input array length
        if(elem > maxVal) maxVal = elem; //time O(1)
    })
    for(let height = maxVal; height>0; height--){ //time O(h) - 'h' being height
        let positions = []; //space O(n) as a max
        for(let j = 0; j < arr.length; j++){ //time O(n)
            if(arr[j] >= height) positions.push(j); //time O(1)
        }
        volume = positions.reduce(function(accVol, curr, index){
            if(index > 0) return accVol + (curr - positions[index - 1] - 1);
            return acc; //time O(1)
        }, volume) //time O(n) max
    }
    return volume;
}

//Overall (after reducing) -- time O(h*n), space O(n)

Alternate Approach

  • Find the highest peaks to the left of each position by rightward scan.  
  • Similarly, find the highest peaks to the right of each position by leftward scan.
  • At each position:
    • Take the minimum peak
    • Subtract the current height (e.g. if the current height is equal to the peak then there is no volume to add)
  • Sum up each position to find total volume
  • Time complexity: O(n)
  • Space complexity: O(n)

Alternate Approach Implemented

function rainCollector(arr){
    var leftMax = [], rightMax = [], max = 0, volume = 0;

    //make arrays the same length as the input,
    //  containing max heights at each index
    for(let i = 0; i < arr.length; i++){
        max = arr[i] > max ? arr[i] : max;
        leftMax[i] = max;
    }
    max = 0; //reset max
    for(let i = arr.length - 1; i >= 0; i--){
        max = arr[i] > max ? arr[i] : max;
        rightMax[i] = max;
    }
    
    //Go through the array summing vertically based on peaks
    //Don't forget to subtract the value of the element itself
    arr.forEach((el, i) => {
        volume += Math.min(leftMax[i], rightMax[i]) - el;
    })
    
    return volume;
}

Alternate Approach Optimizated

function rainCollector(arr){
    var rightMax = [], max = 0, volume = 0;
    
    for(let i = arr.length - 1; i >= 0; i--){
        max = arr[i] > max ? arr[i] : max;
        rightMax[i] = max;
    }
    
    max = 0;
    
    for(let i = 0; i < arr.length; i++){
        max = arr[i] > max ? arr[i] : max;
        volume += Math.min(max, rightMax[i]) - arr[i];
    }
    
    return volume;
}

Solution with comments:

Rainwater Collector

By Kate Humphrey

Rainwater Collector

Technical interview problem for determining water collected between indices

  • 1,597