#algorithm
#алгоритм
Вопрос:
Учитывая массив V, нам нужно найти два индекса (i, j), таких что V[j] > V[i] и (j — i) является максимальным.
Подход грубой силы довольно прост, в котором для каждого значения с индексом i (в диапазоне от 1 до n) мы сравниваем значение с индексом j (в диапазоне от i 1 до n). Мы отслеживаем максимум (j-i) на данный момент, чтобы найти окончательный ответ.
Этот подход имеет временную сложность O (n ^ 2). Есть ли у кого-нибудь предложения по улучшению временной сложности?
Комментарии:
1. @spacevillain Ваш подход не сработал бы, если список [4, 3, 2 ,1 ], потому что после сортировки в порядке неубывания
i
будет 3, аj
будет 0, что неверно, посколькуj
должно быть большеi
.2. @Kay, каково ограничение V [i]?
3. @IvanBenko — Нет ограничений на значения массива, если это то, что вы имеете в виду. Все, что нам нужно найти, это пару индексов (i, j), таких, что A[j] > A [i], и j-i является максимальным (развернутым).
4. [8, 7, 5, 4, 2, 1, 9, 6, 3] это действительно сложный список для поиска.
5. Я могу найти хороший нижний предел для
j - i
в O (n). Сначала найдите две последовательные записи в порядке возрастания. Затем вернитесь к концу массива, ища запись, которая больше, чем нижняя запись. Затем возвращаемся к нижней записи, ища запись ниже верхней записи.
Ответ №1:
Вот подход, который позволит решить проблему за линейное время.
- Вычислить стек S возрастающих позиций,
i
такой, чтоmin A[1..i-1] > i
с помощью простого прямого сканирования по массиву. - Выполнить итерацию по списку в обратном направлении.
- Если текущий элемент больше значения, заданного вершиной стека S: проверьте, есть ли у нас новая запись, и откройте вершину стека.
Быстрая реализация на python:
def notsurewhattonamethis(A):
assert(A)
S = [0]
for i,v in enumerate(A):
if v<A[S[-1]]:
S.append(i)
best = (-1,-1)
for i,v in reversed(list(enumerate(A))):
while v>A[S[-1]]:
j = S.pop()
d = i - j
if d > best[1]-best[0]:
best = (j,i)
if not S: return best
return best
Комментарии:
1. Неплохо, хотя это все еще O (N ^ 2) пространственно-временной сложности.
2. @Neil: Почему вас вообще беспокоит сложность времени * пространства?
3. Только потому, что в исходном вопросе не уточнялось, приемлема ли сложность пространства O (N). (Учитывая принятый ответ, я вижу, что это так.)
Ответ №2:
Если у вас есть известное ограничение элементов массива (иначе смотрите Обновление ниже) Я могу предложить вам алгоритм с временной сложностью O(n*log(MaxN))
и пространственной сложностью O (maxN), где maxN = Max(V [i]). Для этого алгоритма нам нужна структура, которая может достигать минимума в массиве от 1 до N с временной сложностью O (log(N)) и обновлять элемент массива с временной сложностью O (log(N)). Дерево Фенвика может выполнять эти трюки. Давайте назовем эту структуру минимизатором. Затем нам нужно:
- Перебрать все элементы в заданном порядке v[i] и поместить в позицию v [i] при значении минимизатора i.
- Для каждого элемента v [i] найдите минимум, используя минимизатор между 1 и v[i-1] (это минимальный индекс элемента, который меньше v [i])
- Запомните максимальную разницу между i и найденным минимальным индексом элемента, который меньше v[i].
ОК. Я пытался написать некоторый псевдокод:
prepare array (map values)
init minimizator
ansI = -1
ansJ = -1
for i from 0 to v.length-1
minIndexOfElementLessThanCurrent = get min value from 1 to v[i]-1 (inclusive) using minimizator
set to minimizator v[i] position value i
if minIndexOfElementLessThanCurrent is exists
if ansJ - ansI < i - minIndexOfElementLessThanCurrent
ansJ = i
ansI = minIndexOfElementLessThanCurrent
Реализация на C :
class FenwickTree
{
vector<int> t;
int n;
public:
static const int INF = 1000*1000*1000;
void Init (int n)
{
this->n = n;
t.assign (n, INF);
}
int GetMin (int i)
{
int res = INF;
for (; i >= 0; i = (i amp; (i 1)) - 1)
res = min (res, t[i]);
return res;
}
void Update (int i, int value)
{
for (; i < n; i = (i | (i 1)))
t[i] = min (t[i], value);
}
};
pair<int, int> Solve(const vector<int>amp; v)
{
int maxElement = 0;
for(int i = 0; i < v.size(); i )
maxElement = max(maxElement, v[i]);
FenwickTree minimizator;
minimizator.Init(maxElement 1);
int ansI = -1, ansJ = -1;
for(int i = 0; i < v.size(); i )
{
int minLeftIndex = minimizator.GetMin(v[i]-1);
minimizator.Update(v[i], i);
if(minLeftIndex == FenwickTree::INF) continue; // no left elements less than v[i]
if(ansJ - ansI < i - minLeftIndex)
{
ansJ = i;
ansI = minLeftIndex;
}
}
return make_pair(ansI, ansJ);
}
Обновить:
Если вид элементов не является int (например, double) или если максимальное значение элементов массива слишком велико (например, 10 ^ 9), мы можем
сопоставьте значения массива (это не повлияет на результат) с целочисленным набором 1 .. N, и тогда временная сложность должна быть O (n * log(n))
Обновить:
Если элементы являются целыми числами — есть O(max(maxN, n))
решение. Итак, если maxN <= n
сложность равна O(N). Нам просто нужно ответить на запрос «получить минимум от 1 до N» за постоянное время O (1):
- Создать массив размером
maxN
- Элемент m[i] массива является минимальным индексом
i
значения в исходном массиве V. - Используя динамическое программирование, создайте массив такого размера, чтобы элемент
r[i]
массива был минимальнымm[j], 1 <= j <= i
. Рекуррентное соотношение равноr[i] = min(r[i-1], m[i])
Основная идея этого алгоритма та же, что и выше, только используйте array r
для нахождения min от 1
до v[i]
.
C implementation:
pair<int, int> Solve(const vector<int>amp; v)
{
int maxElement = 0;
for(int i = 0; i < v.size(); i )
maxElement = max(maxElement, v[i]);
vector<int> minimum(maxElement 1, v.size() 1);
for(int i = 0; i < v.size(); i )
minimum[v[i]] = min(minimum[v[i]], i); // minimum[i] contains minimum index of element i
for(int i = 1; i < minimum.size(); i )
minimum[i] = min(minimum[i-1], minimum[i]); // now minimum[i] contains minimum index between elements 1 and i
int ansI = -1, ansJ = -1;
for(int i = 0; i < v.size(); i )
{
int minLeftIndex = minimum[v[i]-1];
if(minLeftIndex >= i) continue; // no left elements less than v[i]
if(ansJ - ansI < i - minLeftIndex)
{
ansJ = i;
ansI = minLeftIndex;
}
}
return make_pair(ansI, ansJ);
}
Если элементы двойные или что-то еще (очень большие целые числа), мы не можем сопоставить элементы, чтобы установить 1 .. N за линейное время (или можем?). Я знаю только O(n*log(n))
решение (сортировка элементов и т.д.)
Комментарии:
1. 1 для алгоритма, почти -1 для плохой реализации. Лучше записать это в псевдокоде
Ответ №3:
Реализация Java выполняется за линейное время.
public class MaxIndexDifference {
public static void main(String[] args) {
System.out.println(betweenTwoElements(2, 3, 6, 10, 4));
}
private static int betweenTwoElements(int... nums) {
int numberOfElements = nums.length;
int maxDifference = -1, minIndex = 0, maxIndex = 0;
int[] lMin = new int[numberOfElements];
int[] rMax = new int[numberOfElements];
/* Construct lMin such that stores the minimum value (to the left) from (nums[0], nums[1], ... nums[i])*/
lMin[0] = nums[0];
for (int i = 1; i < numberOfElements; i ) {
lMin[i] = Math.min(nums[i], lMin[i -1]);
}
/* Construct RMax[] such that RMax[j] stores the maximum value (to the right) from (arr[j], arr[j 1], ..arr[n-1]) */
rMax[numberOfElements - 1] = nums[numberOfElements - 1];
for (int i = numberOfElements-2; i >= 0; i--) {
rMax[i] = Math.max(nums[i], rMax[i 1]);
}
/* Traverse both arrays from left to right to find optimum maxIndex - minIndex This process is similar to merge() of MergeSort */
while (minIndex < numberOfElements amp;amp; maxIndex < numberOfElements) {
if (lMin[minIndex] < rMax[maxIndex]) {
maxDifference = Math.max(maxDifference, maxIndex - minIndex);
maxIndex = maxIndex 1;
} else {
minIndex = minIndex 1;
}
}
return maxDifference;
}
}
Ответ №4:
Алгоритм с O (N) сложностью:
#!/usr/bin/perl
use strict;
use warnings;
sub dump_list { print join(", ", map sprintf("-", $_), @_), "n" }
for (0..20) {
# generate a random list of integers with some convenient bias:
my @l = (map int(rand(20) 20 - $_), 0..19);
my $max = $l[-1];
my $min = $l[0];
my @max;
for my $l (reverse @l) {
$max = $l if $l > $max;
push @max, $max;
}
@max = reverse @max;
my @min;
for my $l (@l) {
$min = $l if $l < $min;
push @min, $min;
}
my $best_i = 0;
my $best_j = -1;
my $best = -1;
my $j = 0;
for my $i (0..$#l) {
while ($j < @l) {
last unless $max[$j] > $min[$i];
$j ;
if ($j - $i > $best) {
$best = $j - 1 - $i;
$best_i = $i;
$best_j = $j - 1;
}
}
}
print "list: "; dump_list @l;
print "idxs: "; dump_list 0..$#l;
print "best: $best, i: $best_i, j: $best_jnn";
}
обновление: в ответ на запрос Nohsib:
Допустим, у вас есть случайный список чисел (a[0], a[1], a[2], a [3] …, a [N-1])
Первый шаг — найти для каждого числа максимум слева в виде mas max[i] = maximum(a[0], a[1], ..., a[i])
и минимум справа min[i] = minimum(a[i], a[i 1], ..., a[N-1])
.
Как только у вас есть эти массивы, найти интервал, в котором a[j] < a[k]
он максимизируется k-j
, почти тривиально.
Попробуйте выполнить это на бумаге с несколькими случайными списками, и вы легко узнаете логику, стоящую за этим.
Комментарии:
1. Блестяще! Я думаю, это все равно сработало бы, если бы вместо использования циклов for и while у вас был бы один цикл while, подобный
while ( i < n amp; j < n)
и увеличивающийj
при$max[$j] > $min[$i]
увеличении elsei
. В настоящее время с вашим подходом вы все еще можете получить временную сложность O (n ^ 2) для списка типа[1,2,3,4,4,4,4,4,4,4]
. Но с помощью предложенного выше изменения вы можете уменьшить временную сложность до O (n). Спасибо за идею. Я бы попытался написать псевдокод, когда у меня когда-нибудь получится.2. @Kay: в худшем случае это уже O (N), потому что
$j
не сбрасывается в 0 внутри цикла for. В худшем случае код внутри цикла while выполняется 2 * N раз, и поэтому он равен O (N).
Ответ №5:
Я не уверен, чего вы на самом деле хотите здесь, первый абзац вашего вопроса противоречит второму. Итак, я дам два ответа, по одному для каждой интерпретации, о которой я могу подумать, хотя оба могут не соответствовать тому, что вы имели в виду.
Первая интерпретация: поиск максимального значения V [j]-V [i] при ограничении, что j > i.
Это то, что это почти нахождение минимального и максимального значений. Но, кроме того, существует также ограничение на индексы. Это само по себе не означает, что идея не может быть использована; для любого выбранного V [i] вам просто нужно максимальное значение по сравнению с V [i 1 .. n], и вам не нужно каждый раз пересчитывать эти максимумы, что приводит к этому алгоритму:
- Вычислить максимальный массив суффиксов в O (n)
- Найти пару (V[i], суффиксmax[i 1]) с максимальной разницей в O (n)
Вторая интерпретация: поиск максимального j-i при ограничении, что V[j] > V[i].
Я не могу придумать ничего хорошего.