Minimum total area of at most two banners to cover N buildings

Viewed 957

I am working on this assignment:

There are N rectangular buildings standing along the road next to each other. The Kth building is of size Hk × 1.

Because a renovation of all of the buildings is planned, we want to cover them with rectangular banners until the renovations are finished. Of course, to cover a building, the banner has to be at least as high as the building. We can cover more than one building with a banner if it is wider than 1.

For example, to cover buildings of heights 3, 1, 4 we could use a banner of size 4×3 (i.e. of height 4 and width 3), marked here in blue:

Buildings of sizes (3 × 1), (1 × 1), (4 × 1), covered with scaffolding of size 4×3

We can order at most two banners and we want to cover all of the buildings. Also, we want to minimize the amount of material needed to produce the banners.

What is the minimum total area of at most two banners which cover all of the buildings?

Write a function:

function solution(H);

that, given an array H consisting of N integers, returns the minimum total area of at most two banners that we will have to order.

Examples:

  1. Given H = [3, 1, 4], the function should return 10. The result can be achieved by covering the first two buildings with a banner of size 3×2 and the third building with a banner of size 4×1:
    Illustration of the example

  2. Given H = [5, 3, 2, 4], the function should return 17. The result can be achieved by covering the first building with a banner of size 5×1 and the other buildings with a banner of size 4×3:

  3. Given H = [5, 3, 5, 2, 1], your function should return 19. The result can be achieved by covering the first three buildings with a banner of size 5×3 and the other two with a banner of size 2×2:
    Illustration of the example

  4. Given H = [7, 7, 3, 7, 7], your function should return 35. The result can be achieved by using one banner of size 7×5:
    Illustration of the example

Write an efficient algorithm for the following assumptions:

N is an integer within the range [1..100,000]; each element of array H is an integer within the range [1..10,000].

Here is what I tried:

function solution(H){
    let length = H.length;
    let maxFromLeft = []
    let  maxFromRight = []
    maxFromLeft.length = length+1
    maxFromRight.length = length+1
    
    let currentMax = 0;
    for(let i =0; i< length; i++){
        currentMax = currentMax >= H[i] ? currentMax:H[i]
        maxFromLeft.push(currentMax)
    }
    currentMax =0
    for(let i=length-1; i> -1; i--){
        currentMax= currentMax >= H[i] ? currentMax : H[i]
        maxFromRight.push(currentMax)
    }
    let result = Number.MAX_VALUE;
    for(let i=0; i< length; i++){
        
       result = Math.min(result, maxFromLeft[i] * i  + maxFromRight[i]*(length-1)) 
       
    }
    console.log(result)
    return result   
}

solution([3,1,4]);

My result returns NaN. Where did I make a mistake?

3 Answers

Adding Some changes, Your code has passed all the cases...

Changes:

  1. Reason For NaN: You are initializing the array with empty(Undefined values) as below=>
maxFromLeft.length = length+1

maxFromRight.length = length+1
  1. Overlapping Of Values: Follow the comment of trincot for better understanding... For the Error in this evaluation =>

When 2 banners are used, should they always be side-by-side, and not one above the other (where the upper one does not start from the ground floor, but at the height of the first banner)? – trincot

result = Math.min(result, maxFromLeft[i] * i + maxFromRight[i]*(length-1))

function solution(H){
    let length = H.length;
    var maxFromLeft = []
    var  maxFromRight = []
    
    let currentMax = 0;
    for(var i =0; i< length; i++){
        currentMax = currentMax >= H[i] ? currentMax:H[i]
        maxFromLeft.push(currentMax)
    }
    
    currentMax =0
    for(let i=length-1; i> -1; i--){
        currentMax= currentMax >= H[i] ? currentMax : H[i]
        maxFromRight.push(currentMax)
    }
    maxFromRight.reverse();
    
    let result = Number.MAX_VALUE;
    for(var i=1; i< length; i++){
       result = Math.min(result, maxFromLeft[i-1] * (i)  + maxFromRight[i]*(length-i)) 
    }
    
    console.log(result)
    
    return result;
   
}

solution([3, 1, 4]) 
solution([5, 3, 2, 4])
solution([5, 3, 5, 2, 1])
solution([7, 7, 3, 7, 7]) 

Some issues:

  • The reason for the NaN output is that you initialise the length of both arrays, which means you have arrays with empty slots. When you execute push on such arrays, those pushed values are appended after those empty slots. You should just not set those length attributes. push will do the necessary...

  • maxFromRight[i]*(length-1) is wrong in two ways: first it should take the entry from the end, so the index should be length-1-i instead if i, and the multiplication should be variable as well, and have -i as well: it should be maxFromRight[length-1-i]*(length-1-i)

  • For the same value of i, the values for maxFromLeft[i] and maxFromRight[i] include the consideration of H[i]. There should be no such overlap. i should be used as a clear split between two sections. So you could define that builing i belongs to the second part of the split and not the first. To make that happen, make sure that the first value in maxFromLeft is 0: you can achieve this with a reordering of statements.

I will assume that the banners have to be side-by-side, and not on top of eachother.

Here is your code with the fixes applied:

function solution(H){
    let length = H.length;
    let maxFromLeft = []
    let  maxFromRight = []
    // Remove the lines that set the length!
    
    let currentMax = 0;
    for(let i =0; i< length; i++){
        // next two lines switched, so first entry is 0
        maxFromLeft.push(currentMax)
        currentMax = currentMax >= H[i] ? currentMax:H[i]
    }
    currentMax =0
    for(let i=length-1; i> -1; i--){
        currentMax= currentMax >= H[i] ? currentMax : H[i]
        maxFromRight.push(currentMax)
    }
    let result = Number.MAX_VALUE;
    for (let i=0; i < length; i++){
       result = Math.min(result, maxFromLeft[i] * i  + maxFromRight[length-1-i]*(length-i)) // fix 
    }
    console.log(result)
    return result
   
}

solution([3, 1, 4]); 
solution([5, 3, 2, 4]); 
solution([5, 3, 5, 2, 1]); 
solution([7, 7, 3, 7, 7]); 

The slickest solution that I have seen for this problem is this one:

def solution(H):
# case 1, 1 banner, compute 1 banner first, then see if you can reduce it
one_banner_area = max(H)*len(H)

# case 2, 2 banners

#start at index 1 to avoid the checking an empty sequence
curr_smallest = one_banner_area
for i in range(1, len(H)):
    # use list slicing
    two_banner_area = (max(H[0:i])*len(H[0:i])) + (max(H[i:])*len(H[i:]))

    if two_banner_area < curr_smallest:
        curr_smallest = two_banner_area

if curr_smallest < one_banner_area:
    return curr_smallest
else:
    return one_banner_area
return 0
Related