Алгоритм островного озера

101
8

Резюме


Предположим, у вас есть горный двухмерный остров. Из-за того, насколько это дождливо, все долины на острове полностью заполнены водой, до такой степени, что добавление воды больше не приведет к переполнению озер. Если перелив перейдет в другое озеро, он тоже переполнится. В конце концов, вода будет вытекать из острова. Учитывая форму острова, как вы узнаете, какие озера образуются?


Подробнее


Чтение последовательности чисел (каждое число, представляющее высоту точки в двумерном подключенном графе), вычисляет и выводит высоту поверхности всех озер, которые могут образовываться в долинах острова.


Например, вход 4 3 7 1 3 2 3 5 6 2 1 3 3 2 3 (на фото ниже) должен давать выход 4 6 3 3 во временной сложности не более O(n log n).


Как выглядит алгоритм? Можно ли это сделать в линейной сложности?


Вот код, который у меня есть до сих пор:


import sys

def island_lakes():
lst=[]
lst1=[0]*3
x=[int(i) for i in sys.stdin.readline().split()]
lst.extend(x)

print(lst)

for x in range(len(lst)-1):
if x>0:
lst1[0]=lst[x-1]
lst1[1]=lst[x]
lst1[2]=lst[x+1]
if lst1[1]<lst1[0] and lst1[1]<lst1[2]:
if lst1[0]>lst1[2]:
print(lst1[2])
else:
print(lst1[0])


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


Example


При вводе выше 4 3 7 1 3 2 3 5 6 2 1 3 3 2 3 он должен печатать 4 6 3 3, но моя программа выдает:


4 3 3 2 3


Как исправить мой код, чтобы он мог найти большие долины, например те, которые имеют небольшой пик в них?

спросил(а) 2014-05-09T13:13:00+04:00 5 лет, 11 месяцев назад
1
Решение
91

O (n) решение:


Перейдите слева направо. Помните первый пик, найдите более высокий пик (или одну высоту), затем нарисуйте озеро между ними, чем помните этот более высокий пик и повторите процесс.
Затем сделайте то же самое право налево. Это так просто. (Код не проверен)

def island_lakes():
lst=[]
x=[int(i) for i in sys.stdin.readline().split()]
lst.extend(x)

print(lst)

cur_height = lst[0]
valley_found = false
for i in range(1, len(lst)):
if lst[i] >= cur_height and valley_found:
print(cur_height)
valley_found = false
else:
valley_found = true

if lst[i] >= cur_height:
cur_height = lst[i]

cur_height = lst[-1]
valley_found = false
for i in range(len(lst)-2, -1, -1):
if lst[i] >= cur_height and valley_found:
print(cur_height)
valley_found = false
else:
valley_found = true

if lst[i] >= cur_height:
cur_height = lst[i]

ответил(а) 2014-05-10T15:31:00+04:00 5 лет, 11 месяцев назад
95

Чтобы найти максимальную высоту озер, которые могут потенциально образоваться, вам нужно найти все пики или точки, которые выше, чем точки вокруг них. Немного изменив код, мы можем легко получить пики на одной итерации:


lst = [0] + lst + [0] # Add zeros to either side to avoid having to deal with boundary issues
peaks = []
for x in range(1, len(lst)-1):
if lst[x-1] =< lst[x] >= lst[x+1]: # "x =< y >= z" is the same as "x =< y and y >= z"
peaks.append(lst[x])

Для вашего примера 4 3 7 1 3 2 3 5 6 2 1 3 3 2 3 пики будут


[4, 7, 3, 6, 3, 3, 3]

Теперь нам нужно объединить озера. То, как мы находим, какие озера можно объединить, состоит в том, чтобы пройти список пиков, отслеживая самый высокий пик до сих пор, и для каждого пика мы удаляем любые предыдущие пики, которые ниже его и самый высокий пик до сих пор. Однако это не требует никакой дополнительной информации, поэтому мы можем сделать это в том же цикле for, что и тот, который находит пики в первую очередь:


highest_so_far = 0
for x in range(1, len(lst)-1):
if lst[x-1] =< lst[x] >= lst[x+1]: # "x =< y >= z" is the same as "x =< y and y >= z"
while peaks and highest_so_far > peaks[-1] < lst[x]:
peaks.pop()
if lst[x] > highest_so_far:
highest_so_far = lst[x]
peaks.append(lst[x])

Это уменьшит пики в нашем примере до

[4, 7, 6, 3, 3, 3]

Теперь, чтобы найти все озера, мы просто просматриваем список и выводим нижнюю часть каждой пары. Однако есть морщина - с серией из 3-х, откуда мы знаем, плоская ли она или озеро, разделяющее пики равной высоты? Мы должны знать, находятся ли точки рядом друг с другом или нет. Это можно сделать, включив информацию о местоположении вместе с каждым пиком -


peaks.append((lst[x], x))

Затем, когда мы переходим через окончательный список пиков, мы проверяем нижнюю из двух, и если они равны, мы проверяем, находятся ли они рядом друг с другом (без них нет озера).


Просто чтобы я не писал весь код для вас, я оставлю его вам, чтобы изменить цикл для работы с peaks, содержащим кортежи, и написать цикл, который определяет озера на основе найденных пиков.


Что касается временной сложности, я считаю, что это выполняется в линейном времени. Очевидно, что мы просматриваем список только один раз, но в середине есть цикл while. На каждой итерации цикл while проверяет хотя бы один пик, но если он когда-либо проверяет более одного пика, это происходит потому, что был удален хотя бы один пик. Таким образом, за пределами проверки на одну итерацию ни один пик не будет проверяться несколько раз, что дает не более линейное увеличение времени.

ответил(а) 2014-05-09T15:07:00+04:00 5 лет, 11 месяцев назад
51

Отвечал на аналогичный вопрос и, разумеется, не додумался до этого. Мое решение было легко модифицировать для этого варианта вопроса.
Ключевые моменты:


    Найдите первый пик, начинающийся с каждого конца списка.
    Нижняя из двух пиков является строгой вершиной независимо от того, что происходит посередине острова.
    Начните работать с нижнего пика по направлению к середине.
    Прекратить проверку, когда встречаются левый пик и правый пик (где бы это ни было
    есть).

Есть немного дополнительной бухгалтерской отчетности, чтобы убедиться, что окончательный список находится в правильном порядке (слева направо) и что он учитывает пики, которые являются плоскими точками (плато)


Каждый элемент в списке касается только один раз, поэтому это O (n).


def lakeLevels(island):
llist = [] # list of levels from the left side.
rlist = [] # list of levels from the right side.

lpeak = 0
for i in range(1, len(island)):
if island[i] < island[lpeak]:
break
else:
lpeak = i

rpeak = len(island)-1
for i in range(len(island)-2, 0, -1):
if island[i] < island[rpeak]:
break
else:
rpeak = i

while lpeak < rpeak:
if island[lpeak] < island[rpeak]:
i = lpeak+1
# Following if check handles plateaus.
if island[i] < island[lpeak]:
llist.append(island[lpeak])

while island[i] < island[lpeak]:
i += 1
lpeak = i

else:
i = rpeak-1
# Following if check handles plateaus.
if island[i] < island[rpeak]:
rlist.insert(0,island[rpeak]) # Insert from the
while island[i] < island[rpeak]:
i -= 1
rpeak = i

return llist + rlist

ответил(а) 2015-03-18T05:54:00+03:00 5 лет назад
37

У меня также есть решение проблемы, я также добавил некоторые комментарии в код:


public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] tokens = br.readLine().split(" ");
int[] arr = new int[tokens.length];

for (int i = 0; i < tokens.length; i++) {
arr[i] = Integer.parseInt(tokens[i]);
}

Stack<Integer> stack = new Stack<Integer>();

// biggestRight[i] stores the index of the element which is the greatest from the ones to the right of i
int[] biggestRight = new int[tokens.length];

// toRight[i] stores the first index to the right where the element is greater or equal to arr[i]
int[] toRight = new int[tokens.length];
int biggestIndex = -1;

for (int i = arr.length - 1; i >= 0; i--) {
biggestRight[i] = biggestIndex;

if (biggestIndex == -1 || (biggestIndex != -1 && arr[i] >= arr[biggestIndex])) {
biggestIndex = i;
}
}

for (int i = arr.length - 1; i >= 0; i--) {
while (!stack.isEmpty() && arr[stack.peek()] < arr[i]) {
stack.pop();
}

if (stack.isEmpty()) {
toRight[i] = -1;
} else {
toRight[i] = stack.peek();
}

stack.push(i);
}

System.out.println(Arrays.toString(biggestRight));
System.out.println(Arrays.toString(toRight));

/**
* Iterate from left to right
* When we are at arr[i]:
* (1) if toRight[i] != -1 -> this means that there is a possible valley starting at position i (we need to check the width of it)
* (2) if toRight[i] == -1 -> this means that we are at a peak and so we search for the biggest element to the right of i, because they constitute a valley
* (3) otherwise just move to the right by one
*/
int i = 0;
while (i < arr.length) {
if (toRight[i] != -1 && toRight[i] > i + 1) {
System.out.println(Math.min(arr[toRight[i]], arr[i]));
i = toRight[i];
} else if (toRight[i] == -1 && biggestRight[i] != -1 && biggestRight[i] > i + 1) {
System.out.println(Math.min(arr[biggestRight[i]], arr[i]));
i = biggestRight[i];
} else {
i++;
}
}
}

ответил(а) 2014-11-11T01:07:00+03:00 5 лет, 4 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема