Число всех возрастающих подпоследовательностей в данной последовательности?

Возможно, вы слышали о хорошо известной проблеме нахождения самой длинной растущей подпоследовательности. Оптимальный алгоритм имеет сложность O(n*log(n)).

Я думал о проблеме нахождения all растущих подпоследовательностей в заданной последовательности. Я нашел решение проблемы, в которой нам нужно найти ряд растущих подпоследовательностей длины k, который имеет сложность O(n*k*log(n)) (где n - длина последовательности).

Конечно, этот алгоритм может быть использован для моей проблемы, но тогда решение имеет сложность O(n*k*log(n)*n) = O(n^2*k*log(n)), я полагаю. Я думаю, что должно быть лучшее (я имею ввиду - быстрее) решение, но пока не знаю.

Если вы знаете, как решить проблему нахождения всех возрастающих подпоследовательностей в заданной последовательности в оптимальном времени/сложности (в данном случае оптимальном = лучше, чем O(n^2*k*log(n))), пожалуйста, сообщите мне об этом.

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

Ответы

Ответ 1

Я не знаю, является ли это оптимальным - возможно, нет, но здесь решение DP в O(n^2).

Пусть dp[i] = number of increasing subsequences with i as the last element

for i = 1 to n do
    dp[i] = 1
    for j = 1 to i - 1 do
        if input[j] < input[i] then
            dp[i] = dp[i] + dp[j] // we can just append input[i] to every subsequence ending with j

Тогда это просто вопрос суммирования всех записей в dp

Ответ 2

Вы можете вычислить количество возрастающих подпоследовательностей в O (n log n) времени следующим образом.

Вспомним алгоритм длины самой длинной возрастающей подпоследовательности:

Для каждого элемента вычислите элемент предшественника среди предыдущих элементов и добавьте его к этой длине.

Этот алгоритм работает наивно в O (n ^ 2) времени и работает в O (n log n) (или даже лучше, в случае целых чисел), если вы вычисляете предшественника, используя структуру данных, такую ​​как сбалансированная двоичная дерево поиска (BST) (или что-то более продвинутое, как дерево Van Emde Boas для целых чисел).

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

Как предостережение, обратите внимание, что число возрастающих последовательностей может быть очень большим, так что арифметика больше не занимает O (1) раз за операцию. Это необходимо учитывать.

Psuedocode:

ret = 0
T = empty_augmented_bst() // with an integer field in addition to the key
for x int X:

  // sum of auxiliary fields of keys less than x
  // computed in O(log n) time using augmented BSTs
  count = 1 + T.sum_less(x)

  T.insert(x, 1 + count) // sets x auxiliary field to 1 + count
  ret += count // keep track of return value

return ret

Ответ 3

Я предполагаю без потери обобщения вход A [0.. (n-1)] состоит из всех целых чисел в {0, 1,..., n-1}.

Пусть DP [i] = число возрастающих подпоследовательностей, заканчивающихся на A [i].

Мы имеем рекуррентность:

DP[i] = 1 + \sum_{j < i, A[j] < A[i]} DP[j]

Для вычисления DP [i] нам нужно только вычислить DP [j] для всех j, где A [j] А [I]. Поэтому мы можем вычислить массив DP в порядке возрастания значений A. Это оставляет DP [k] = 0 для всех k, где A [k] > A [i].

Проблема сводится к вычислению суммы DP [0] в DP [i-1]. Предположим, что мы уже вычислили DP [0] в DP [i-1], мы можем вычислить DP [i] в ​​O (log n), используя дерево Фенвика.

Окончательный ответ - это DP [0] + DP [1] +... DP [n-1]. Алгоритм работает в O (n log n).

Ответ 4

Это решение O (nklogn), где n - длина входного массива, а k - размер возрастающих подпоследовательностей. Он основан на решении упомянутом в вопросе.

vector<int> values, массив длины n - это массив, который нужно искать для увеличения подпоследовательностей.

vector<int> temp(n); // Array for sorting
map<int, int> mapIndex; // This will translate from the value in index to the 1-based count of values less than it

partial_sort_copy(values.cbegin(), values.cend(), temp.begin(), temp.end());

for(auto i = 0; i < n; ++i){
    mapIndex.insert(make_pair(temp[i], i + 1)); // insert will only allow each number to be added to the map the first time
}

mapIndex теперь содержит ранжирование всех чисел в values.

vector<vector<int>> binaryIndexTree(k, vector<int>(n)); // A 2D binary index tree with depth k
auto result = 0;

for(auto it = values.cbegin(); it != values.cend(); ++it){
    auto rank = mapIndex[*it];
    auto value = 1; // Number of sequences to be added to this rank and all subsequent ranks
    update(rank, value, binaryIndexTree[0]); // Populate the binary index tree for sub-sequences of length 1

    for(auto i = 1; i < k; ++i){ // Itterate over all sub-sequence lengths 2 - k
        value = getValue(rank - 1, binaryIndexTree[i - 1]); // Retrieve all possible shorter sub-sequences of lesser or equal rank
        update(rank, value, binaryIndexTree[i]); // Update the binary index tree for sub sequences of this length
    }
    result += value; // Add the possible sub-sequences of length k for this rank
}

После размещения всех n элементов values во все k размерности binaryIndexTree. value, собранный в result, представляет собой общее число возрастающих подпоследовательностей длины k.

Функции двоичного индекса, используемые для получения этого результата:

void update(int rank, int increment, vector<int>& binaryIndexTree)
{
    while (rank < binaryIndexTree.size()) { // Increment the current rank and all higher ranks
        binaryIndexTree[rank - 1] += increment;
        rank += (rank & -rank);
    }
}

int getValue(int rank, const vector<int>& binaryIndexTree)
{
    auto result = 0;
    while (rank > 0) { // Search the current rank and all lower ranks
        result += binaryIndexTree[rank - 1]; // Sum any value found into result
        rank -= (rank & -rank);
    }
    return result;
}

Дерево двоичных индексов, очевидно, O (nklogn), но это способность последовательно заполнять его, что создает возможность использовать его для решения.

mapIndex создает ранг для каждого числа в values, так что наименьшее число из values имеет ранг 1. (Например, если values равно "2, 3, 4, 3, 4, 1", то mapIndex будет содержать: "{1, 1}, {2, 2}, {3, 3}, {4, 5}". Обратите внимание, что "4" имеет ранг "5", 2 "3" s в values

binaryIndexTree имеет k разных деревьев, уровень x будет представлять общее количество увеличивающих подстрок, которые могут быть сформированы из длины x. Любое число в values может создать подстроку длиной 1, поэтому каждый элемент будет увеличивать ее ранг и все ранги над ней на 1.
На более высоких уровнях возрастающая подстрока зависит от того, что уже имеется подстрока, имеющая более короткую длину и более низкий ранг.

Поскольку элементы вставляются в двоичное дерево индексов в соответствии с их порядком в values, порядок вхождения в values сохраняется, поэтому, если элемент был вставлен в binaryIndexTree, потому что он предшествует текущему элементу в values.

Отличное описание того, как дерево двоичных индексов доступно здесь: http://www.geeksforgeeks.org/binary-indexed-tree-or-fenwick-tree-2/

Вы можете найти исполняемую версию кода здесь: http://ideone.com/GdF0me

Ответ 5

Возьмем пример -

Возьмем массив {7, 4, 6, 8}
Теперь, если вы рассматриваете каждый отдельный элемент также как подпоследовательность, то число возрастающей подпоследовательности, которая может быть сформирована, -
{7} {4} {6} {4,6} {8} {7,8} {4,8} {6,8} {4,6,8}
Для этого массива может быть сформирована общая 9 возрастающая подпоследовательность. Итак, ответ: 9.

Код выглядит следующим образом:

int arr[] = {7, 4, 6, 8};
int T[] = new int[arr.length];

for(int i=0; i<arr.length; i++)
    T[i] = 1;    

int sum = 1;     
for(int i=1; i<arr.length; i++){
    for(int j=0; j<i; j++){
        if(arr[i] > arr[j]){
            T[i] = T[i] + T[j]; 
        }
    }
    sum += T[i];
}
System.out.println(sum);

Сложность кода - O (N log N).

Ответ 6

Java-версия в качестве примера:

    int[] A = {1, 2, 0, 0, 0, 4};
    int[] dp = new int[A.length];

    for (int i = 0; i < A.length; i++) {
        dp[i] = 1;

        for (int j = 0; j <= i - 1; j++) {
            if (A[j] < A[i]) {
                dp[i] = dp[i] + dp[j];
            }
        }
    }