Динамическое программирование: можно ли найти интервал четных 1 и 0 в линейном времени? - PullRequest
3 голосов
/ 06 августа 2011

Нашел в интернете следующую информацию:

У вас есть массив 0 и 1, и вы хотите вывести все интервалы (I, J), где число 0 и число 1 равны. Пример

    pos = 0 1 2 3 4 5 6 7 8
          0 1 0 0 1 1 1 1 0

Один интервал (0, 1), потому что там число 0 и 1 равны. Есть много других интервалов, найти их все в линейном времени.

Я думаю, что нет линейного временного алгоритма, так как может быть n ^ 2 таких интервалов. Я прав? Как я могу доказать, что существует n ^ 2 таких?

Ответы [ 4 ]

8 голосов
/ 06 августа 2011

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

Пусть L - ваш исходный список чисел, а A - хэш пустых массивов, где изначально A [0] = [0]

sum = 0
for i in 0..n
  if L[i] == 0:
    sum--
    A[sum].push(i)
  elif L[i] == 1:
    sum++
    A[sum].push(i)

Теперь A - это, по сути, график x y суммы последовательности (x - это индекс списка, y - это сумма). Каждый раз, когда есть два значения x x1 и x2 для значения y, у вас есть интервал (x1, x2], где число 0 и 1 равно.

Существует m (m-1) / 2 (арифметическая сумма от 1 до m - 1) интервалов, где сумма равна 0 в каждом массиве M в A, где m = M.length

Используя ваш пример для вычисления A вручную, мы используем этот график

L           #   0  1  0  1  0  0  1  1  1  1  0
A keys      0  -1  0 -1  0 -1 -2 -1  0  1  2  1
L index    -1   0  1  2  3  4  5  6  7  8  9  10

(Я добавил #, чтобы представить начало списка с ключом -1. Также удалил все числа, которые не являются 0 или 1, так как они просто отвлекают) A будет выглядеть так:

[-2]->[5]
[-1]->[0, 2, 4, 6]
[0]->[-1, 1, 3, 7]
[1]->[8, 10]
[2]->[9]

Для любого M = [a1, a2, a3, ...], (ai + 1, aj), где j> i будет интервалом с тем же числом 0s, что и 1s. Например, в [-1] -> [0, 2, 4, 6] интервалы составляют (1, 2), (1, 4), (1, 6), (3, 4), (3, 6), (5, 6).

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

Если, конечно, вы не считаете, что построения A достаточно, чтобы найти все интервалы (поскольку из A очевидно, каковы интервалы), то оно линейно по n: P

2 голосов
/ 06 августа 2011

Возможно линейное решение (извините, ранее я утверждал, что это должно быть n ^ 2), если вы осторожны, чтобы на самом деле не печатать результаты!

Во-первых, давайте определим «оценку» для любого набора нулей и единиц как количество единиц минус число нулей. Таким образом, (0,1) имеет оценку 0, в то время как (0) равно -1, а (1,1) равно 2.

Теперь начните справа. Если крайняя правая цифра равна 0, то ее можно объединить с любой группой слева, имеющей 1 балл. Таким образом, нам нужно знать, какие группы доступны слева, проиндексированные по баллу. Это предполагает рекурсивную процедуру, которая накапливает группы с оценками. Процесс развертки - это O (n), и на каждом этапе он должен проверять, создала ли он новую группу, и расширять таблицу известных групп. Проверка для новой группы является постоянным временем (поиск в хеш-таблице). Расширение таблицы известных групп также является постоянным временем (сначала я думал, что это не так, но вы можете поддерживать отдельное смещение, которое позволяет избежать обновления каждой записи в таблице).

Таким образом, у нас есть особая ситуация: каждый шаг процесса идентифицирует набор результатов размером O (n), но для этого необходимо вычислить постоянное время (в пределах этого шага). Таким образом, сам процесс по-прежнему O (n) (пропорционально количеству шагов). Конечно, на самом деле печать результатов (либо на шаге, либо в конце) делает вещи O (n ^ 2).

Я напишу немного кода Python для тестирования / демонстрации.

Вот и мы:

SCORE = [-1,1]

class Accumulator:

    def __init__(self):
        self.offset = 0
        self.groups_to_right = {} # map from score to start index
        self.even_groups = []
        self.index = 0

    def append(self, digit):
        score = SCORE[digit]
        # want existing groups at -score, to sum to zero
        # but there's an offset to correct for, so we really want
        # groups at -(score+offset)
        corrected = -(score + self.offset)
        if corrected in self.groups_to_right:
            # if this were a linked list we could save a reference
            # to the current value.  it's not, so we need to filter
            # on printing (see below)
            self.even_groups.append(
                (self.index, self.groups_to_right[corrected]))
        # this updates all the known groups
        self.offset += score
        # this adds the new one, which should be at the index so that
        # index + offset = score (so index = score - offset)
        groups = self.groups_to_right.get(score-self.offset, [])
        groups.append(self.index) 
        self.groups_to_right[score-self.offset] = groups
        # and move on
        self.index += 1
        #print self.offset
        #print self.groups_to_right
        #print self.even_groups
        #print self.index

    def dump(self):
        # printing the results does take longer, of course...
        for (end, starts) in self.even_groups:
            for start in starts:
                # this discards the extra points that were added
                # to the data after we added it to the results
                # (avoidable with linked lists)
                if start < end:
                    print (start, end)

    @staticmethod
    def run(input):
        accumulator = Accumulator()
        print input
        for digit in input:
            accumulator.append(digit)
        accumulator.dump()
        print

Accumulator.run([0,1,0,0,1,1,1,1,0])

И вывод:

dynamic: python dynamic.py 
[0, 1, 0, 0, 1, 1, 1, 1, 0]
(0, 1)
(1, 2)
(1, 4)
(3, 4)
(0, 5)
(2, 5)
(7, 8)

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

Может показаться удивительным, что результат имеет размер O (n ^ 2), в то время как процесс поиск результатов равен O (n), но легко увидеть, как это возможно: на одном «шаге» процесс идентифицирует количество групп (размером O (n)), связывая текущую точку (self.index в append или end в dump()) со списком конечных точек (self.groups_to_right[...] или ends).

Обновление: еще один момент. Таблица «групп справа» будет иметь «типичную ширину» записей sqrt (n) (это следует из центральной предельной теоремы - это в основном случайное блуждание в 1D). Поскольку запись добавляется на каждом шаге, средняя длина также равна sqrt (n) (значения n распределяются по ячейкам sqrt (n)). Это означает, что ожидаемое время для этого алгоритма (т.е. со случайными входами), если вы включите печать результатов, составляет O (n ^ 3/2), даже если наихудший случай равен O (n ^ 2)

1 голос
/ 06 августа 2011

Отвечая непосредственно на вопрос:

вам нужно построить пример, в котором имеется более O (N) совпадений:

пусть N будет в форме 2 ^ k со следующимввод:

 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1  (here, N=16)

количество совпадений (где 0 - начальный символ):

length    #
2         N/2 
4         N/2 - 1
6         N/2 - 2
8         N/2 - 3
..
N         1

Общее количество совпадений (начиная с 0): (1 + N / 2) * (N / 2) / 2 = N ^ 2/8 + N / 4

Совпадения, начинающиеся с 1, почти одинаковы, ожидайте, что для каждой длины они на один меньше.

Итого: (N ^ 2/8 + N / 4) * 2 - N / 2 = N ^ 2/4

0 голосов
/ 07 августа 2011

Каждый интервал будет содержать хотя бы одну последовательность либо (0,1), либо (1,0).Следовательно, это просто вопрос обнаружения каждого вхождения (0,1) или (1,0), а затем для каждого просмотра, если оно находится рядом с существующим решением или два элемента bookend образуют другое решение.

с небольшимОб хитрости хранилища вы сможете найти все решения за линейное время.Перечислять их будет O (N ^ 2), но вы должны быть в состоянии закодировать их в O (N) пространстве.

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