给定建筑物高度的水累积算法

时间:2014-12-26 01:36:29

标签: python algorithm

我正在练习算法,而且我已经坚持了几天这个问题。当我测试我的解决方案时,我仍然是错误的。这是问题陈述:

  

纽约华尔街以其令人叹为观止的摩天大楼而闻名。   但是下雨的季节即将来临,而且水的数量也在增加   将落在建筑物上将是巨大的今年。以来   每栋建筑都被固定在左侧和右侧的建筑物上   (除了第一个和最后一个),水可以从a泄漏   只有当建筑物的高度高于高度时才建造   建筑物的左侧或右侧(边缘的高度)   华尔街是0)。所有建筑物的宽度均为1米。您   给出了华尔街建筑物的高度(米)   从左到右,您的任务是打印到标准输出   (stdout)持有的总水量(立方米)   华尔街的建筑物。

示例输入:

heights: [9 8 7 8 9 5 6]

示例输出:

5

说明: 在这个例子中,在第一个和第五个建筑物之间有4立方米的水(1个超过第二个,2个超过第三个,1个超过第四个),而在第五个和第七个建筑物之间有1立方米持有水(在第六座建筑物上)。

我解决这个问题的方法是找到全局最大值,并使用这些最大值的差异来计算积水量。我考虑到最后使用local_water变量可能遗漏的水。任何人都可以帮我找到算法或代码中的错误吗?

注意:我正在寻找一种只能通过每个元素的解决方案

以下是我输入错误的输入:

heights: [8,8,4,5]

此输入应该产生1,而不是我的回答0

这是我的代码:

def skyscrapers(heights):
    heights.insert(0,0)
    heights.append(0)
    local_max = 0
    global_max = 0
    total_water = 0
    local_water = 0
    end_water = []
        # end_water records water heights to be used for finding 
                # water between the final global maximum and 
                # subsequent local maximums. These potential values are
                # stored in local_water.
    for i in range(1, len(heights)-1):
        end_water.append(heights[i]) 

        # check for local max
        if heights[i-1] < heights[i] and heights[i] > heights[i+1]:

            # record potential collected water for after final
            # gloabl max
            for s in end_water:
                local_water += (heights[i] - s) * (heights[i] - s > 0)
            local_max=i

        # new global max
        if heights[local_max] > heights[global_max]:
            for s in heights[global_max:local_max]:
                if heights[global_max] - s > 0:
                    total_water += heights[global_max] - s
            global_max = local_max
            local_water = 0
            end_water = []

    total_water += local_water

    print total_water

5 个答案:

答案 0 :(得分:6)

height
   _       _
9 | |_   _| |      _ _
8 |   |_|   |     |   |
7 |         |  _  |   |
6 |         |_| | |   |  _
5 |             | |   |_| |
4 |             | |       |  _       _ 
3 |             | |       | | |  _  | |
2 |             | |       | | |_| |_| |
1 |0 1 2 3 4 5 6| |0 1 2 3| |0 1 2 3 4| pos

这是基于stack-based solution Maximize the rectangular area under Histogram问题的单通(!)(O(n) - 时间)O(n)空间算法:

from collections import namedtuple

Wall = namedtuple('Wall', 'pos height')

def max_water_heldover(heights):
    """Find the maximum amount of water held over skyscrapers with *heights*."""
    stack = []
    water_held = 0 # the total amount of water held over skyscrapers
    for pos, height in enumerate(heights):
        while True:
            if not stack or height < stack[-1].height: # downhill
                stack.append(Wall(pos + 1, height)) # push possible left well wall
                break
            else: # height >= stack[-1].height -- found the right well wall/bottom
                bottom = stack.pop().height
                if stack: # if there is the left well wall
                    well_height = min(stack[-1].height, height) - bottom
                    if well_height:
                        water_held += (pos - stack[-1].pos) * well_height
    return water_held

示例:

>>> max_water_heldover([9, 8, 7, 8, 9, 5, 6])
5
>>> max_water_heldover([8, 8, 4, 5])
1
>>> max_water_heldover([3, 1, 2, 1, 3])
5

我已经用蛮力O(n * m)算法对其进行了测试:

from itertools import product

def test(max_buildings=6, max_floors=6):
    for nbuildings, nfloors in product(range(max_buildings), range(max_floors)):
        for heights in product(range(nfloors), repeat=nbuildings):
            assert max_water_heldover(heights) == max_water_heldover_bruteforce(heights), heights

其中max_water_heldover_bruteforce()是:

import sys
from colorama import Back, Fore, Style, init # $ pip install colorama
init(strip=not sys.stderr.isatty())

def max_water_heldover_bruteforce(heights):
    if not heights: return 0
    BUILDING, AIR, WATER = ['B', ' ',
            Back.BLUE + Fore.WHITE + 'W' + Fore.RESET + Back.RESET + Style.RESET_ALL]
    matrix = [[WATER] * len(heights) for _ in range(max(heights))]
    for current_floor, skyscrapers in enumerate(matrix, start=1):
        outside = True
        for building_number, building_height in enumerate(heights):
            if current_floor <= building_height:
                outside = False
                skyscrapers[building_number] = BUILDING
            elif outside:
                skyscrapers[building_number] = AIR
        for i, building_height in enumerate(reversed(heights), 1):
            if current_floor > building_height:
                skyscrapers[-i] = AIR
            else:
                break
    if '--draw-skyscrapers' in sys.argv:
        print('\n'.join(map(''.join, matrix[::-1])), file=sys.stderr)
        print('-'*60, file=sys.stderr)
    return sum(1 for row in matrix for cell in row if cell == WATER)

结果是一样的。

答案 1 :(得分:6)

这是一个改进了刘志东和J.S.的一次性解决方案。 Sebastian的解决方案只使用O(1)空间:

def fillcount(elevations):
    start = 0
    end = len(elevations) - 1
    count = 0
    leftmax = 0
    rightmax = 0

    while start < end:
        while start < end and elevations[start] <= elevations[start + 1]:
            start += 1
        else:
            leftmax = elevations[start]

        while end > start and elevations[end] <= elevations[end - 1]:
            end -= 1
        else:
            rightmax = elevations[end]

        if leftmax < rightmax:
            start += 1
            while start < end and elevations[start] <= leftmax:
                count += leftmax - elevations[start]
                start += 1
        else:
            end -= 1
            while end > start and elevations[end] <= rightmax:
                count += rightmax - elevations[end]
                end -= 1

    return count

我针对这个更简单的双程解决方案进行了测试:

def fillcount_twopass(elevations):
    global_max = max(range(len(elevations)), key=lambda x: elevations[x])
    count = 0
    local_max = 0

    for i in xrange(0, global_max):
        if elevations[i] > local_max:
            local_max = elevations[i]
        else:
            count += local_max - elevations[i]

    local_max = 0
    for i in xrange(len(elevations) - 1, global_max, -1):
        if elevations[i] > local_max:
            local_max = elevations[i]
        else:
            count += local_max - elevations[i]

    return count

双程解决方案基于以下逻辑:

  1. 查找整个图表的最大峰值 - 全局最大值。
  2. 从左侧扫描到全局最大峰值。跟踪左边的最大值。每个单元格 a)低于或等于左边最大 b)左边最大右侧, c)左边全球最大值将保持水量。当左边的最大值增加时,它对早期的列没有影响,但是后面的列现在将水保持在这个新的最大值。
  3. 从右边做同样的事情。
  4. 这与Rémi建议的类似,但使用全局最大值来提供锚点,这简化了事情。

    一次性解决方案部分基于Mark Tolonen的想法。它通过观察我们可以同时执行左右传递,而不知道全局最大值来改进上述内容。这是因为任何一方的当前最大值要么大于,低于或等于另一方的最大值。即使我们还不知道全局最大值是多少,所以较低的最大值总是低于全局最大值,因此我们可以从那里继续到那一侧的下一个局部最大值。该算法详细说明:

    1. 从列表的startend开始,并将left_maxright_maxcount初始化为0
    2. 向右扫描,递增start,直到达到左边最大值。然后向左扫描,递减end,直到达到正确的最大值。
    3. 从较低的最大值开始,继续扫描,直至到达大于局部最大值的列,沿途计算可填充单元格并将其添加到count
    4. 重复步骤2和3,在startend重合时结束。
    5. 请注意,就我们的目的而言,局部最大值只是在上升之前的任何点(可能是一个高原),然后是下降。到目前为止遇到的最高局部最大值以下的局部最大值仅在步骤3中遇到,其中它们没有效果。

      最后一个解决方案可以在3秒内处理一千万个数据点:

      >>> rands = [random.randrange(0, 1000000) for i in xrange(10000000)]
      >>> %timeit fillcount(rands)
      1 loops, best of 3: 3.3 s per loop
      

答案 2 :(得分:2)

class Solution:


    # @param A, a list of integers
    # @return an integer
    def trap(self, A):
        uTrapper = []

        i = 0
        leftIndex = 0
        rightIndex = 0
        # only 2 left could not trap water
        while (i < (len(A) - 2)):
            leftIndex = self.findLeftBank((A[i:])) + i

            rightIndex = self.findRightBank((A[leftIndex+1:]), A[leftIndex]) + leftIndex + 1

            uTrapper.append((A[leftIndex : rightIndex+1]))
            i = rightIndex

        return self.getRainWater(uTrapper)

    def findLeftBank(self, list):
        for i in range(len(list)):
            curr = list[i]
            currNext = list[i+1] if i+1 < len(list) else 0

            if(curr > currNext):
                return i
        return len(list) - 1

    def findRightBank(self, list, leftHight):
        biggestLoc = len(list)
        biggest = 0
        for i in range(len(list)):
            if(list[i] >= leftHight):
                return i
            if(list[i] > biggest):
                biggestLoc = i
                biggest = list[i]

        return biggestLoc

    def getRainWater(self, lists):

        all = 0
        for i in range(len(lists)):
            list = lists[i]

            height = min(list[0], list[len(list)-1])
            for i in range(1, len(list)-1):
                if(list[i] > height):
                    continue
                all = all + (height - list[i])
        return all

s = Solution()
print s.trap([9,6,8,8,5,6,3])

上面好吗?

答案 3 :(得分:0)

我想提出一种看起来很直观的解决方案。

战略:该策略是在建筑物的高度中找到局部最大值,并在每对最大值(如果有)的中间,以累积水量:

代码

from scipy.signal import argrelextrema
import numpy as np

def local_max_scipy(a):
    minima = argrelextrema(a, np.greater_equal)[0]
    return minima

def water_cumulative(buildings):
    local_maxima=local_max_scipy(buildings)
    water = 0
    # 2 or more maxima => there is water 
    if len(local_maxima)>1:
        # in the middle of every couple of local maxima
        for i in range((len(local_maxima)-1)):
            reference = 0
            #pick the shorter building between the two maxima as reference
            if buildings[local_maxima[i]] >= buildings[local_maxima[i+1]]:
                reference = buildings[local_maxima[i+1]]
            else:
                reference = buildings[local_maxima[i]]
            #cumulate the water
            for j in range(local_maxima[i+1]-local_maxima[i]):
                # just exit when building higher than the reference is found
                if buildings[local_maxima[i]+1+j] < reference:
                    water = water + reference - buildings[local_maxima[i]+1+j]
                else:
                    break
    return water 

测试

该功能已通过以下测试:

buildings01 = np.array([3, 2, 1, 4, 5])

buildings02 = np.array([1, 2, 3, 4, 5])

buildings03 = np.array([9, 8, 7, 8, 9, 5, 6])

buildings04 = np.array([8, 8, 4, 5])

输出为:

>>>water_cumulative(buildings01)
3
>>>water_cumulative(buildings02)
0
>>>water_cumulative(buildings03)
5
>>>water_cumulative(buildings04)
1

答案 4 :(得分:0)

我将使用R软件提供答案。我对此的态度 问题如下:创建3个功能:

  1. Startchoice()选择开始的矢量,例如舍弃连续的建筑物的高度 不低于自己。

  2. SectorCalculation()使用Startchoice()返回的向量创建向量的小列表,以计算建筑物之间的水域。如果在第一个值之后找到局部最大值或全局最大值,则计算机将迭代向量的索引值,直到找到以下局部最大值或全局最大值,并将向量的子集存储到列表中的该点为止,从而消除了该问题。向量的一部分,但最后一个值再次将其传递给公式,并重做相同的步骤,直到 向量全部完成。

  3. 然后,AreaCalculation()将用于为输出列表中的每个向量计算它们可以处理的水。

main()函数仅获取初始向量,调用上述函数并求和 由建筑物的每个小向量得出,并给出整数的一般结果。下面提供了一些示例:

SectorCalculation<- function(vector){
  ## It outputs in a list the different side roofs
  counter<-1
  vectorList<- list()
  ## While vector is larger than 2
  ## Choose the max value in the string appart from the left value
  ### If it finds it, then that is the sector, eliminate it and start again 
  ###     the procedure with the part of the vector left
  ### Else , go to next index 
  while(length(vector)>2){
    vecleft<-StartChoice(vector)
    if(all.equal(vecleft,rep(0,length(vecleft)))!=TRUE){
      global_max<- max(vecleft[2:length(vecleft)])
      left<- vecleft[1]
      for(i in 2:length(vecleft)){
        if(i > length(vecleft)){
          return(vectorList)}
        else{
          if(vecleft[i]==global_max){
            vectorList[[counter]]<-vecleft[1:i]
            vector<- vecleft[i:length(vecleft)]
            counter<- counter+1
            break
          }
        }
      }
    }else{return(0)}
  }
  return(vectorList)
}


StartChoice<- function(vecHeights){
  ## It gives back the vector discarding zero values at the beginning 
  leftval<-integer(0)
  ChooseStart <- TRUE  
  for(i in 1:length(vecHeights)){
    if(length(leftval)==0){
      leftval[1]<- vecHeights[i]
    } 
    else if(i == length(vecHeights)){return(0)}
    else {
      if(vecHeights[i] >= leftval){
        leftval[1]<- vecHeights[i]
      }
      else{
        ChooseStart <- FALSE
        vectorleft<-vecHeights[(i-1):length(vecHeights)]
        break
      }
    }
  }
  return(vectorleft)
}



AreaCalculation<-function(HeightsPassed){
  ## Select the second highest value between left and right and 
  ## compute the value in the roof
  highest<- min(HeightsPassed[1], HeightsPassed[length(HeightsPassed)])  
  Res<-(highest-HeightsPassed)[(highest-HeightsPassed)>0] 
  # Selecting only those values higher than 0 
  TotWater<-sum(Res)  
  return(TotWater)
}

main<-function(Heights){
  ## if value containing values <= 0, out
  if(all.equal((Heights <= 0), rep(FALSE, length(Heights)))!=TRUE){
    stop("Either values equal or lower than 0 or non-numeric values supplied")
  } 
  ## Get in a list all the sectors to be calculated
  MyHeightslist<-SectorCalculation(Heights)
  ## If list different than a list 0 with a 0, then 
  ## just calculate by parts the water in the roofs; then sum it
  if(all.equal(MyHeightslist[[1]],rep(0,length(MyHeightslist[[1]])))!=TRUE)
  {
    byParts<-sapply(MyHeightslist, AreaCalculation) 
    TotalWater<-sum(byParts)
  }
  else{return(0)}
  return(TotalWater)
}

下面提供了一些示例以了解其工作原理:),

main(c(1,2,3))
[1] 0
main(c(9,8,7,8,9,5,6)) 
[1] 5
main(c(8,9,9,8,7,8,9,5,6))
[1] 5
main(c(8, 8, 4, 5))
[1] 1
main(c(3,1,3,1,1,3))
[1] 6
main(c(1,2,3,4))
[1] 0
main(c(3,2,1))
[1] 0

干杯!,