Алгоритм решения для накопления воды при заданных высотах здания

Я практикую алгоритмы, и я застрял в этой проблеме в течение нескольких дней. Когда я проверяю свое решение, я по-прежнему ошибаюсь. Вот описание проблемы:

Уолл-стрит в Нью-Йорке известна своими захватывающими небоскребами. Но приближается сезон дождей, и количество воды, которое упадет, здания в этом году будут огромными. поскольку каждое здание прикреплено к зданиям слева и справа (за исключением первого и последнего), вода может течь из если высота здания выше высоты от здания до его левого или правого (высота краев Уолл-стрит - 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

Ответ 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

Здесь однопроходный (!) (O (n) -time) 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)

Результаты те же.

Ответ 2

Здесь однопроходное решение, которое улучшается на liuzhidong и J.S. Решения Себастьяна, используя только 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

Двухпроходное решение основано на следующей логике:

  • Найти максимальный пик для всего графика - глобальный максимум.
  • Сканировать с левой стороны на глобальный максимальный пик. Следите за левым максимумом. Каждая ячейка a) ниже или на том же уровне, что и левый максимум, b) справа от левого максимума и c) слева глобального максимума будет содержать воду. Когда левый максимум увеличивается, он не влияет на ранние столбцы, но более поздние столбцы теперь удерживают воду на этом новом максимальном уровне.
  • Сделайте то же самое справа, наоборот.

Это похоже на то, что предложил Rémi, но использует глобальный максимум для обеспечения якоря, что упрощает работу.

Однопроходное решение частично основано на идеях Марка Толонен. Это улучшает вышеизложенное, наблюдая, что мы можем одновременно выполнять как левый, так и правый проход , не зная глобального максимума. Это потому, что текущий максимум с обеих сторон либо больше, чем или меньше максимума на другой стороне. Нижний максимум всегда будет ниже глобального максимума, даже если мы еще не знаем, что такое глобальный максимум, поэтому мы можем перейти оттуда к следующему локальному максимуму с этой стороны. Алгоритм в полной мере:

  • Начните с указателей в start и end списка и инициализируйте left_max, right_max и count до 0.
  • Сканировать вправо, увеличивая start до достижения максимального значения слева. Затем сканируйте влево, уменьшая end, пока не достигнете правого максимума.
  • Из нижнего максимума продолжайте сканирование до тех пор, пока не достигнете столбца больше локального максимума, подсчитав заполняемые ячейки по пути и добавив их к count.
  • Повторите шаги 2 и 3, заканчивающиеся, когда start и end совпадают.

Обратите внимание, что для наших целей локальный максимум - это просто любая точка, которой предшествует восхождение (и, возможно, плато), а затем спуск. Локальные максимумы ниже максимального локального максимума, встречающегося до сих пор, встречаются только на шаге 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

Ответ 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])

Является выше нормально?