Самый длинный подрамник, элементы которого образуют непрерывную последовательность

Учитывая несортированный массив положительных целых чисел, найдите длину самого длинного подмассива, элементы которого при сортировке непрерывны. Можете ли вы придумать решение O (n)?

Пример:

{10, 5, 3, 1, 4, 2, 8, 7}, ответ 5.

{4, 5, 1, 5, 7, 6, 8, 4, 1}, ответ 5.

В первом примере подматрица {5, 3, 1, 4, 2} при сортировке может образовывать непрерывную последовательность 1,2,3,4,5, которые являются самыми длинными.

Для второго примера подматрица {5, 7, 6, 8, 4} является субаром результата.

Я могу думать о методе, который для каждого подмассива, проверяет, равен ли (максимум - минимум + 1) длину этого подмассива, если это правда, то это непрерывный подмассива. Возьмите самый длинный из всех. Но это O (n ^ 2) и не может иметь дело с дубликатами.

Может ли кто-нибудь дать лучший метод?

Ответы

Ответ 1

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

Вход: [a1, a2, a3,...]

Отобразить исходный массив как пару, где 1-й элемент - это значение, а 2nd - индекс массива.

Массив: [[a1, i1], [a2, i2], [a3, i3],...]

Сортируйте этот массив пар с некоторым алгоритмом O (n) (например, Counting Sort) для целочисленной сортировки по значению. Мы получаем еще один массив:

Массив: [[a3, i3], [a2, i2], [a1, i1],...]

где a3, a2, a1,... находятся в отсортированном порядке.

Запустить цикл через отсортированный массив пар

В линейном времени мы можем обнаружить последовательные группы чисел a3, a2, a1. Последовательное определение группы следующее value = prev значение + 1. Во время этого сканирования сохраняйте текущий размер группы (n), минимальное значение индекса ( min) и текущая сумма индексов ( actualSum).

На каждом шаге внутри последовательной группы мы можем оценить сумму индексов, поскольку они создают арифметическую прогрессию с первым элементом min, шагом 1 и размером группы, видимой до сих пор п. Эту оценку суммы можно сделать в O (1) раз, используя формулу для арифметической прогрессии:

оценка sum = (a1 + an) * n/2;

оценка sum = (min + min + (n - 1)) * n/2;

оценка sum = min * n + n * (n - 1)/2;

Если на каком-либо шаге цикла внутри последовательной групповой оценки сумма равна фактической сумме, то наблюдаемая до сих пор последовательная группа удовлетворяет условиям. Сохраните n как текущий максимальный результат или выберите максимум между текущим максимумом и n.

Если на элементах значения мы перестаем видеть последовательную группу, тогда reset все значения и делаем то же самое.

Пример кода: https://gist.github.com/mishadoff/5371821

Ответ 2

UPD2:. Следующее решение для проблемы, когда не требуется, чтобы подмассив был смежным. Я неправильно понял постановку проблемы. Не удаляя это, так как у кого-то может быть идея, основанная на моей, которая будет работать для реальной проблемы.


Вот что я придумал:

Создайте экземпляр словаря (который реализуется как хеш-таблица, давая O (1) в обычных ситуациях). Ключи представляют собой целые числа, значения - хэш-множества целых чисел (также O (1)) - var D = new Dictionary<int, HashSet<int>>.

Итерации через массив A и для каждого целого n с индексом i do:

  • Проверьте, содержатся ли ключи n-1 и n+1 в D.
    • Если ни один из ключей не существует, выполните D.Add(n, new HashSet<int>)
    • если существует только один из ключей, например. n-1, do D.Add(n, D[n-1])
    • Если оба ключа существуют, выполните D[n-1].UnionWith(D[n+1]); D[n+1] = D[n] = D[n-1];
  • D[n].Add(n)

Теперь пройдите через каждую клавишу в D и найдите хэш-набор с наибольшей длиной (длина поиска - O (1)). Наибольшая длина будет ответом.

Насколько я понимаю, наихудшей сложностью будет O (n * log (n)), только из-за операции UnionWith. Я не знаю, как вычислить среднюю сложность, но она должна быть близка к O (n). Пожалуйста, поправьте меня, если я ошибаюсь.

UPD: Говорить код, здесь тестовая реализация на С#, которая дает правильный результат в обоих примерах OP:

var A = new int[] {4, 5, 1, 5, 7, 6, 8, 4, 1};
var D = new Dictionary<int, HashSet<int>>();

foreach(int n in A)
{
    if(D.ContainsKey(n-1) && D.ContainsKey(n+1))
    {
        D[n-1].UnionWith(D[n+1]);
        D[n+1] = D[n] = D[n-1];
    }
    else if(D.ContainsKey(n-1))
    {
        D[n] = D[n-1];
    }
    else if(D.ContainsKey(n+1))
    {
        D[n] = D[n+1];
    }
    else if(!D.ContainsKey(n))
    {
        D.Add(n, new HashSet<int>());
    }

    D[n].Add(n);
}

int result = int.MinValue;
foreach(HashSet<int> H in D.Values)
{
    if(H.Count > result)
    {
        result = H.Count;
    }
}

Console.WriteLine(result);

Ответ 3

См. массив S в этом определении математического набора:

S = U j = 0 k (I j)

Где я j - непересекающиеся целые сегменты. Вы можете создать определенное дерево интервалов (на основе дерева Red-Black или дерева самобалансировки, которое вам нравится:)) для хранения массива в этих математических определениях. Структуры node и дерева должны выглядеть так:

struct node {
    int d, u;
    int count;
    struct node *n_left, *n_right;
}

Здесь d - меньшая граница целочисленного отрезка, а u - верхняя граница. count добавляется, чтобы учесть возможные дубликаты в массиве: при попытке вставить уже существующий элемент в дерево вместо того, чтобы ничего не делать, мы увеличим значение count node, в котором оно найдено.

struct root {
    struct node *root;
}        

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

Учитывая три узла P, L и R, L - левый ребенок из P и R - правый ребенок P. Затем вы должны обеспечить выполнение L.u < P.d и P.u < R.d(и для каждого node, d <= u, конечно).

При вставке целочисленного сегмента [x, y] вы должны найти "перекрывающиеся" сегменты, то есть интервалы [u, d], которые удовлетворяют одному из следующих неравенств:

y >= d - 1
ИЛИ
x <= u + 1

Если вставленный интервал является singleton x, вы можете найти только до двух перекрывающихся интервальных узлов N1 и N2, таких как N1.d == x + 1 и N2.u == x - 1. Затем вам необходимо объединить два интервала и количество обновлений, что оставляет вас с N3 таким, что N3.d = N2.d, N3.u = N1.u и N3.count = N1.count + N2.count + 1. Поскольку дельта между N1.d и N2.u является минимальной дельта для двух сегментов, которые должны быть непересекающимися, то вы должны иметь одно из следующих значений:

  • N1 - правильный дочерний элемент N2
  • N2 - левый дочерний элемент N1

Таким образом, в худшем случае вставка будет < <212 > .

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

Ответ 4

Для этого потребуется два прохода над данными. Сначала создайте хэш-карту, сопоставив ints с bools. Я обновил свой алгоритм, чтобы не использовать карту, из STL, в которой я уверен, использует сортировку внутри. Этот алгоритм использует хеширование и может быть легко обновлен для любой максимальной или минимальной комбинации, даже потенциально все возможные значения, которые может получить целое число.

#include <iostream>

using namespace std;
const int MINIMUM = 0;
const int MAXIMUM = 100;
const unsigned int ARRAY_SIZE = MAXIMUM - MINIMUM;

int main() {

bool* hashOfIntegers = new bool[ARRAY_SIZE];
//const int someArrayOfIntegers[] = {10, 9, 8, 6, 5, 3, 1, 4, 2, 8, 7};
//const int someArrayOfIntegers[] = {10, 6, 5, 3, 1, 4, 2, 8, 7};
const int someArrayOfIntegers[] = {-2, -3, 8, 6, 12, 14,  4, 0, 16, 18, 20};
const int SIZE_OF_ARRAY = 11;

//Initialize hashOfIntegers values to false, probably unnecessary but good practice.
for(unsigned int i = 0; i < ARRAY_SIZE; i++) {
    hashOfIntegers[i] = false;
}

//Chage appropriate values to true.
for(int i = 0; i < SIZE_OF_ARRAY; i++) {
    //We subtract the MINIMUM value to normalize the MINIMUM value to a zero index for negative numbers.
    hashOfIntegers[someArrayOfIntegers[i] - MINIMUM] = true;
}

int sequence = 0;
int maxSequence = 0;
//Find the maximum sequence in the values
for(unsigned int i = 0; i < ARRAY_SIZE; i++) {

    if(hashOfIntegers[i]) sequence++;
    else sequence = 0;

    if(sequence > maxSequence) maxSequence = sequence;
}

cout << "MAX SEQUENCE: " << maxSequence << endl;
return 0;
}

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

Ответ 5

Не надейтесь, это всего лишь частичный ответ.

Я уверен, что проблема не разрешима в O(n). К сожалению, я не могу это доказать.

Если существует способ решить его менее чем за O(n^2), я бы предположил, что решение основано на следующей стратегии:

  • Решите в O(n) (или, может быть, O(n log n)), существует ли непрерывная субарма, как вы ее описываете, по крайней мере, с элементами i. Позволяет называть этот предикат E(i).
  • Используйте bisection, чтобы найти максимум i, для которого выполняется E(i).

Общее время работы этого алгоритма будет O(n log n) (или O(n log^2 n)).

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

Ответ 6

вот еще один способ подумать о вашей проблеме: предположим, что у вас есть массив, состоящий только из 1s и 0s, вы хотите найти самый длинный последовательный запуск 1s. это можно сделать в линейном времени по длине кодирования 1s (игнорировать 0). чтобы преобразовать исходную проблему в эту новую проблему с кодировкой длины пробега, вы вычисляете новый массив b [i] = (a [i] < a [i + 1]). это не нужно делать явно, вы можете просто сделать это неявно для достижения алгоритма с постоянной потребностью в памяти и линейной сложностью.

Ответ 7

Вот 3 приемлемых решения:

Первое - это O(nlog(n)) во времени и O(n) пробел, второе - O(n) во времени и O(n) в пространстве, а третья - O(n) во времени и O(1) в пространстве.

  • постройте a binary search tree, затем выполните в порядке.
    держите 2 указателя один для начала максимального подмножества и один для конца. сохраняйте значение max_size во время итерации дерева. это O(n*log(n)) сложность времени и пространства.

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

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

    • вычислить сумму массива и произведение массива
    • выяснить, какие цифры у вас есть, если у вас есть минимальный и максимальный исходный набор. В целом это временная сложность O(n).