Самый быстрый способ найти минимальное произведение из 2 элементов массива, содержащих более 200 000 элементов - PullRequest
13 голосов
/ 12 января 2020

У меня есть массив a[n]. Число n вводится нами. Мне нужно найти минимальное произведение a[i] и a[j], если:

1) abs(i - j) > k

2) a[i] * a[j] свернуто

Вот мой решение (очень наивное):

#include <iostream>
using namespace std;
#define ll long long
int main() {
    ll n,k; cin >> n >> k;

    ll a[n]; for(ll i=0;i<n;i++) cin >> a[i];

    ll mn; bool first = true;

    for(ll i=0;i<n;i++) {
        for(ll j=0;j<n;j++) {
            if(i!=j)
            if(abs(i-j) > k) {
                if(first) {
                    mn = a[i]*a[j];
                    first = false;
                } else if(a[i]*a[j] < mn) mn = a[i]*a[j];
            }
        }
    }
    cout << mn << endl;
}

Но я хочу знать, есть ли более быстрый способ найти минимальный продукт с расстоянием?

Ответы [ 3 ]

12 голосов
/ 12 января 2020

Предполагая, что есть хотя бы одна пара элементов, удовлетворяющих условиям, и нет умножения двух элементов в нем, переполняется, это можно сделать за Theta(n-k) время и Theta(1) пространство в наихудшем и лучшем случае, с чем-то вроде этого :

auto back_max = a[0];
auto back_min = a[0];
auto best = a[0]*a[k+1];

for(std::size_t i=1; i<n-(k+1); ++i) {
    back_max = std::max(back_max, a[i]);
    back_min = std::min(back_min, a[i]);
    best = std::min(best, std::min(a[i+k+1]*back_max, a[i+k+1]*back_min));
}

return best;

Это оптимально с точки зрения асимптотики c сложность наихудшего случая как для времени, так и для пространства, поскольку оптимальным произведением может быть a[0] с любым из элементов n-(k+1) на расстоянии не менее k+1, поэтому любой алгоритм, решающий проблему, должен прочитать не менее n-(k+1) целых чисел.


Идея алгоритма заключается в следующем:

Оптимальный продукт использует два элемента a, предположим, что это a[r] и a[s]. Без ограничения общности можно предположить, что s > r, поскольку произведение является коммутативным.

Из-за ограничения abs(s-r) > k это означает, что s >= k+1. Теперь s может быть каждым из индексов, удовлетворяющих этому условию, поэтому мы перебираем эти индексы. Это итерация по i в показанном коде, но для удобства она сдвинута на k+1 (на самом деле не имеет значения). Для каждой итерации нам нужно найти оптимальный продукт, включающий i+k+1 в качестве наибольшего индекса, и сравнить его с предыдущим наилучшим предположением.

Возможные индексы для пары i+k+1 - все индексы меньше или равны i из-за требования расстояния. Нам также нужно было бы повторить все это, но это не является необходимым, поскольку минимум a[i+k+1]*a[j] сверх j при фиксированном i равен min(a[i+k+1]*max(a[j]), a[i+k+1]*min(a[j])) из-за монотонности продукта (принимая минимум за как для минимума, так и для максимума, превышающего a[j], учитываются два возможных признака a[i+k+1] или, эквивалентно, два возможных направления монотонности.)

Поскольку набор значений a[j], для которых мы оптимизируем здесь, равен просто {a[0], ..., a[i]}, который просто увеличивается на один элемент (a[i]) в каждой итерации i, мы можем просто отслеживать max(a[j]) и min(a[j]) с отдельными переменными, обновляя их, если a[i] больше или меньше, чем предыдущие оптимальные значения. Это делается с помощью back_max и back_min в примере кода.

Первый шаг итерации (i=0) пропускается в l oop и вместо этого выполняется как инициализация переменных.

6 голосов
/ 12 января 2020

Не уверен насчет Самый быстрый .

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

Итак, (следующее слишком сложно, см. ответ грецкого ореха )
(• balk, если k ≤ n
• инициализировать minProduct к [0] * a [k + 1])

  • сохранить две динамические c минимальные структуры данных upToI и beyondIplusK
    начиная с {} и {a [ j ] | k j }
  • для каждого i от 0 до n - k - 1
    • добавить [ i ] к upToI
    • удалить [ i + k ] из beyondIplusK
    • проверка на наличие нового минимального продукта среди
      мин ( upToI ) × мин ( beyondIplusK ), мин ( upToI ) × максимум ( beyondIplusK ),
      max ( upToI ) × min ( beyondIplusK ) и максимум ( upToI ) × макс ( beyondIplusK )
4 голосов
/ 12 января 2020

Для "минимальной величины"

Найдите 2 элемента "наименьшей величины", затем (после того, как вы нашли два нуля или произвели поиск по всему массиву), умножьте их.

Для «наименьшего значения» без abs(i - j) > k детали

Существует 3 варианта:

  • два наивысших (наименьших величина) отрицательные числа

  • два наименьших (наименьшая величина) неотрицательных числа

  • наименьшее (наибольшая величина) отрицательное число и наибольшее (наибольшая величина) неотрицательное число

Вы можете выполнить поиск по всем 6 значениям и выяснить, какие продукты являются лучшими в конце.

Однако; как только вы видите ноль, вы знаете, что вам больше не нужно знать о первых двух возможностях; и как только вы видите одно отрицательное число и одно неотрицательное число, вы знаете, что вас волнует только третья возможность.

Это приводит к конечному автомату с 3 состояниями - «заботятся обо всех 3 возможностях» , «ответ равен нулю, если не видно отрицательного числа» и «заботится только о последней возможности». Это может быть реализовано в виде набора из 3 циклов, где 2 из циклов переходят (goto) в середину другого l oop, когда изменяется состояние (конечного автомата).

В частности , это может выглядеть примерно так (не проверено):

   // It could be any possibility

   for(ll i=0;i<n;i++) {
       if(a[i] >= 0) {
            if(a[i] < lowestNonNegative1) {
                lowestNonNegative2 = lowestNonNegative1;
                lowestNonNegative1 = a[i];
            }
            if(lowestNonNegative2 == 0) {
                goto state2;
            }
       } else {
            if(a[i] > highestNegative1) {
                highestNegative2 = highestNegative1;
                highestNegative1= a[i];
            }
            if(lowestNonNegative1 < LONG_MAX) {
                goto state3;
            }
       }
   }
   if(lowestNonNegative2 * lowestNonNegative1 < highestNegative2 * highestNegative1) {
       cout << lowestNonNegative2 * lowestNonNegative1;
   } else {
       cout << highestNegative2 * highestNegative1;
   }
   return;

   // It will be zero, or a negative and a non-negative

   for(ll i=0;i<n;i++) {
state2:
       if(a[i] < 0) {
           goto state3;
       }
   }
   cout << "0";
   return;

   // It will be a negative and a non-negative

   for(ll i=0;i<n;i++) {
state3:
       if(a[i] < lowestNegative) {
           lowestNegative = a[i];
       } else if(a[i] > highestNonNegative) {
           highestNonNegative = a[i];
       }
    }
    cout << lowestNegative * highestNonNegative;
    return;

Для «наименьшего значения» с abs(i - j) > k part

В этом случае у вас все еще есть 3 возможности; и может заставить его работать с тем же подходом «3 цикла с конечным автоматом», но он становится слишком грязным / безобразным. В этом случае лучшей альтернативой может быть предварительное сканирование массива, чтобы определить, есть ли нули и все ли они отрицательные или все положительные; так что после предварительного сканирования вы можете узнать, что ответ равен нулю, или выбрать al oop, предназначенный только для указанной c возможности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...