【问题标题】:Algorithm to solve for water accumulation given building heights给定建筑物高度的积水算法
【发布时间】:2014-12-26 01:36:29
【问题描述】:

我正在练习算法,我在这个问题上卡了几天。当我测试我的解决方案时,我仍然不正确。这是问题陈述:

纽约的华尔街以其令人叹为观止的摩天大楼而闻名。 但是雨季快到了,水量 今年会倒塌的建筑物将是巨大的。自从 每个建筑物都粘在它的左边和右边的建筑物上 (除了第一个和最后一个),水可以从一个 仅当建筑物的高度高于高度时才建造 建筑物的左侧或右侧(边缘的高度 华尔街的指数是 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

【问题讨论】:

    标签: python algorithm


    【解决方案1】:
    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 的单程 (!) (O(n)-time) O(n)-space 算法,用于解决 Maximize the rectangular area under Histogram 问题:

    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)
    

    结果是一样的。

    【讨论】:

    • @kilojoules:我已经针对暴力 O(n*m) 解决方案测试了 O(n) 算法。两种解决方案产生相同的结果。 max_water_heldover(heights) 很可能是正确的。
    【解决方案2】:

    这里有一个一次性解决方案,改进了 liuzhidong 和 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. 从右边做同样的事情,反过来。

    这类似于Rémi 建议的,但使用全局最大值来提供锚点,这简化了事情。

    一次性解决方案部分基于Mark Tolonen 的想法。它通过观察我们可以同时进行左传和右传,不知道全局最大值,对上述情况进行了改进。这是因为任一侧的 当前 最大值大于、小于或等于另一侧的最大值。较低的最大值总是低于全局最大值,即使我们还不知道全局最大值是多少,所以我们可以从那里继续到那一侧的下一个局部最大值。算法详解:

    1. 从列表的startend处的指针开始,将left_maxright_maxcount初始化为0
    2. 向右扫描,递增start,直到达到左侧最大值。然后向左扫描,递减end,直到达到右侧最大值。
    3. 从较低的最大值继续扫描,直到达到大于局部最大值的列,沿途计算可填充单元格并将它们添加到count
    4. 重复步骤 2 和 3,当 startend 重合时结束。

    请注意,就我们的目的而言,局部最大值只是前面有上升(也可能是高原),然后是下降的任何点。低于迄今为止遇到的最高局部最大值的局部最大值仅在步骤 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
    

    【讨论】:

    • 如果有多个点是全局最大值怎么办?这就是为什么我认为你不能一次完成。每个单元需要知道之前单元的最大值后续单元的最大值,以计算它可以容纳多少水。
    • 在这种情况下,进程从左到右来回循环,直到两边都处于全局最大值。但随后它们相等,因此 if 块的 else 子句启动。当两个局部最大值相等时,阶段 3 扫描从右侧开始。它不断计数细胞,直到两侧在最左边的全局最大值处相遇。
    • @MarkTolonen 见上文。我很确定代码是正确的,但是如果您找到特定的反例或错误,请告诉我!再想一想,我觉得这一定是雷米的想法,他或她只是在暗示。
    • 我无法在阅读代码时将其可视化,因此我只是通过一些示例逐步完成了它,它看起来很棒!干得好!
    • 它仍然存在一些缺陷,例如 c(8,9, 8, 7, 8, 9, 5, 6)
    【解决方案3】:
    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])
    

    以上可以吗?

    【讨论】:

    • 我正在寻找一种只通过每个元素一次的解决方案。抱歉,不清楚。我已经编辑了我的问题以反映这一点。
    • 我的解决方法是...从头开始,然后找到水的地方的左岸,然后从左索引开始找到右岸....然后放范围[leftindex , rightindex] 成一个列表(ok,那就从rightindex开始,再做同样的工作)……最后计算出列表中的水……
    【解决方案4】:

    我想提出看起来非常直观的解决方案。

    策略:策略是找到建筑物高度的局部最大值,并在每两个最大值的中间(如果有的话)累积水:

    代码

    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
    

    【讨论】:

      【解决方案5】:

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

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

      2. SectorCalculation() 使用 Startchoice() 返回的向量来创建向量的小列表来计算建筑物之间的水域面积。如果在第一个值之后找到局部或全局最大值,则计算机将遍历向量的索引值,直到找到以下局部或全局最大高度,将向量的子集存储到列表中的该点,消除向量的一部分,但最后一个值再次将其传递给公式并重做相同的 preocedure 直到 a 矢量就完成了。

      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
      

      干杯!,

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-24
        • 1970-01-01
        • 1970-01-01
        • 2011-11-13
        • 2012-06-13
        • 1970-01-01
        • 2023-04-02
        • 1970-01-01
        相关资源
        最近更新 更多