Найти кратчайший субарах, содержащий все элементы

Предположим, что у вас есть массив чисел и другой набор чисел. Вы должны найти кратчайший субарак, содержащий все числа с минимальной сложностью.

В массиве могут быть дубликаты, и пусть набор чисел не имеет значения. Он не упорядочен - подмассива может содержать набор чисел в любом порядке.

Например:

Array: 1 2 5 8 7 6 2 6 5 3 8 5
Numbers: 5 7

Тогда самым коротким подмассивом, очевидно, является Array[2:5] (обозначение питона).

Также, что бы вы сделали, если хотите по какой-либо причине избежать сортировки массива (а-ля онлайн-алгоритмы)?

Ответ 1

Доказательство решения линейного времени

Я напишу правое расширение для обозначения увеличения правой конечной точки диапазона на 1, а левое сжатие - для увеличения левого конца диапазона на 1. Этот ответ является небольшим изменением Ответ Aasmund Eldhuset. Разница здесь в том, что как только мы найдем наименьший j, такой, что [0, j] содержит все интересные числа, мы будем рассматривать только диапазоны, содержащие все интересные числа. (Возможно интерпретировать Aasmund таким образом, но также можно интерпретировать его как позволяющее потерять один интересный номер из-за сокращения слева - алгоритма, правильность которого еще не установлена).

Основная идея заключается в том, что для каждой позиции j мы найдем кратчайший удовлетворяющий диапазон, заканчивающийся в позиции j, учитывая, что мы знаем наименьший удовлетворяющий диапазон, заканчивающийся в позиции j-1.

РЕДАКТИРОВАТЬ: Исправлен сбой в базовом корпусе.

Базовый регистр: найдите наименьшее j ', так что [0, j'] содержит все интересные числа. По построению не может быть никаких диапазонов [0, k < j '], которые содержат все интересные числа, поэтому нам не нужно беспокоиться о них дальше. Теперь найдите наименьший наибольший i, такой, что [i, j '] содержит все интересные числа (т.е. Удерживайте j' фиксированным). Это наименьший удовлетворяющий диапазон, заканчивающийся в позиции j '.

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

Поскольку мы выполняем это для каждой правой позиции j, мы можем взять диапазон минимальной длины по всем крайним правым положениям, чтобы найти общий минимум. Это можно сделать, используя вложенный цикл, в котором j продвигается по каждому циклу внешнего цикла. Ясно, что j продвигается на 1 n раз. Поскольку в любой момент времени нам нужно только самое левое положение наилучшего диапазона для предыдущего значения j, мы можем сохранить это в я и просто обновлять его по мере продвижения. я начинается с 0, всегда во всех случаях <= j <= n, и только когда-либо продвигается вверх на 1, то есть он может продвигаться не более n раз. И i, и j продвигаются не более чем в n раз, что означает, что алгоритм является линейным.

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

# x[0..m-1] is the array of interesting numbers.
# Load them into a hash/dictionary:
For i from 0 to m-1:
    isInteresting[x[i]] = 1

i = 0
nDistinctInteresting = 0
minRange = infinity
For j from 0 to n-1:
    If count[a[j]] == 0 and isInteresting[a[j]]:
        nDistinctInteresting++
    count[a[j]]++

    If nDistinctInteresting == m:
        # We are in phase 2: contract the left side as far as possible
        While count[a[i]] > 1 or not isInteresting[a[i]]:
            count[a[i]]--
            i++

        If j - i < minRange:
            (minI, minJ) = (i, j)

count[] и isInteresting[] - хеши/словари (или простые массивы, если задействованные числа малы).

Ответ 2

Это похоже на проблему, которая хорошо подходит для подхода с раздвижным окном: поддерживать окно (подмассива), которое постепенно расширяется и сжимается, и использует хэш-карту для отслеживания количества раз, сколько "интересных" номеров появляется в окне. Например. начните с пустого окна, затем разверните его, чтобы включить только элемент 0, затем элементы 0-1, затем 0-2, 0-3 и т.д., добавив последующие элементы (и используя hashmap для отслеживания того, какие числа существуют в окне). Когда хэш-карта сообщает вам, что все интересные номера существуют в окне, вы можете начать сжимать его: например. 0-5, 1-5, 2-5 и т.д., Пока вы не узнаете, что окно больше не содержит всех интересных номеров. Затем вы можете начать расширять его с правой стороны и так далее. Я вполне (но не полностью) уверен, что это сработает для вашей проблемы, и оно может быть реализовано для запуска в линейном времени.

Ответ 3

Скажем, что массив имеет n элементов, а set имеет m элементов

Sort the array, noting the reverse index (position in the original array)
// O (n log n) time

for each element in given set
   find it in the array
// O (m log n) time  - log n for binary serch, m times

keep track of the minimum and maximum index for each found element

min - max определяет ваш диапазон

Общая временная сложность: O ((m + n) log n)

Ответ 4

Это решение определенно не выполняется в O (n) времени, как было предложено некоторым псевдокодом выше, однако это реальный (Python) код, который решает проблему, и мои оценки пробегают в O (n ^ 2):

def small_sub(A, B):
    len_A = len(A)
    len_B = len(B)

    sub_A = []
    sub_size = -1
    dict_b = {}

    for elem in B:
        if elem in dict_b:
            dict_b[elem] += 1
        else:
            dict_b.update({elem: 1})

    for i in range(0, len_A - len_B + 1):
        if A[i] in dict_b:
            temp_size, temp_sub = find_sub(A[i:], dict_b.copy())

            if (sub_size == -1 or (temp_size != -1 and temp_size < sub_size)):
                sub_A = temp_sub
                sub_size = temp_size

    return sub_size, sub_A

def find_sub(A, dict_b):
    index = 0
    for i in A:
        if len(dict_b) == 0:
            break

        if i in dict_b:
            dict_b[i] -= 1

            if dict_b[i] <= 0:
                del(dict_b[i])

        index += 1

    if len(dict_b) > 0:
        return -1, {}
    else:
        return index, A[0:index]