Число всех возрастающих подпоследовательностей в данной последовательности?
Возможно, вы слышали о хорошо известной проблеме нахождения самой длинной растущей подпоследовательности. Оптимальный алгоритм имеет сложность 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];
}
}
}