Найшвидший спосіб знайти мінімальний добуток із двох елементів масиву, що містять 200000+ елементів


13

У мене є масив 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;
}

Але я хочу знати, чи існує швидший спосіб знайти відстань з мінімальним продуктом?


7
Чому я не повинен #include <bits / stdc ++. H>? і C ++ надають лише масив змінної довжини шляхом розширення компілятора. Чому ви не використовуєте std::vector? @Scheff - сортування знищить початкові відносини "на відстань".
Девід К. Ранкін

3
Принаймні перевірку if (i!=j) if (abs(i - j) > k)можна усунути. Просто почніть внутрішню петлю I + до + 1: for (ll j = i + k + 1; j < n; ++j). Перевірку з firstдопомогою також можна усунути, якщо mnвона ініціалізована до, наприклад, з mn = a[0] * a[k + 1];. (Можливо, kслід nспочатку перевірити, щоб зробити цю кулезахисну.) Але це все ще O (N²). Це потрібно зробити швидше ...
Scheff

2
@PaulMcKenzie Будь ласка, покажіть один запит з не менше двох корисних звернень серед першої десятки для мінімального продукту з індексною відстані (або максимальною).
сіра борода

1
@PaulMcKenzie "Мабуть, сотні, якщо не тисячі посилань URL-адрес, які відображають відповіді на це питання." - поділіться принаймні трьома з цих URL-адрес.
גלעד ברקן

2
Звідки взялося це запитання? Це не здається чимось, що складається просто з повітря. Я не був би здивований, якби це було з одного з таких сайтів "онлайн-судді". Якщо так, на цих сайтах, ймовірно, давно розгорнуті дискусії щодо вирішення проблеми, якщо не повного рішення.
PaulMcKenzie

Відповіді:


12

Якщо припустити, що існує хоча б одна пара елементів, що задовольняє умовам, і множення двох елементів у ній не переповнюється, це можна зробити в 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;

Це оптимально з точки зору асимптотичної найгіршої складності як для часу, так і для простору, оскільки оптимальний добуток може бути принаймні 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) пропускається в циклі і замість цього виконується як ініціалізація змінних.


3
@greybeard Мені не потрібно їх тримати, тому що єдині можливі кандидати для оптимального продукту a[i+k+1]- це мінімум та максимум.
волоський горіх

Ви могли б пояснити, чому алгоритм працює у вашій відповіді?
MinaHany

6

Не впевнений у швидкому .

Для більш простої задачі без i <j - k мінімальний добуток є серед продуктів пар з двох найменших і найбільших елементів.

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

  • підтримуйте дві динамічні структури даних minmax вверхToI і даліIplusK,
    починаючи з {} і {a [ j ] | kj }
  • для кожного i від 0 до n - k - 1
    • додайте [ i ] до upToI
    • видаліть [ i + k ] з-за межіIplusK
    • перевірка на новий мінімальний продукт серед
      min ( upToI ) × min ( понадIplusK ), min ( upToI ) × max ( понадIplusK ),
      max ( upToI ) × min ( понадIplusK ) та max ( upToI ) × max ( понадIplusK )

Це має бути найшвидшим, принаймні складним. Це час (n) часу та зберігання.
smttsp

оригінальне рішення має складність O (N ** 2), як ви оцінюєте складність свого рішення?
lenik

O (nlogn) час, O (n) простір (для відповідних реалізацій minmax)
сіра борода

@greybeard. Для чого вам потрібен n * час для входу. Чому б не просто зберігаючи * п масив 4 , який містить minUpto, maxUpto, minBeyond, maxBeyond(Ви можете створити в двох ітерацій)? Потім в третій ітерації для кожного індексу знайдіть мінімально можливе множення.
smttsp

(@smttsp Це було б альтернативним кроком у напрямку розчину волоського горіха .)
сіра борода

4

Для "мінімальної величини"

Знайдіть 2 елементи «найменшої величини», а потім (після того, як ви знайшли дві нулі або шукали весь масив), помножте їх.

Для "найнижчого значення" без abs(i - j) > kчастини

Є 3 можливості:

  • два найвищих (найменшої величини) від’ємних чисел

  • два найменших (найменшої величини) негативних чисел

  • найнижча (найбільша за величиною) негативна кількість та найвища (найбільша величина) негативна кількість

Ви можете шукати всі 6 значень і з'ясувати продукти, і що найкраще в кінці.

Однак; як тільки ви побачите нуль, ви знаєте, що вам не потрібно більше знати про перші 2 можливості; і як тільки ви побачите одне негативне число і одне негативне число, ви дізнаєтесь, що вас хвилює лише третя можливість.

Це призводить до створення кінцевої машини з трьома станами - "дбайте про всі 3 можливості", "відповідь нульова, якщо не видно негативне число" і "дбайте лише про останню можливість". Це може бути реалізовано у вигляді набору з 3-х циклів, де 2 петлі перестрибують у ( goto) середину іншого циклу, коли стан (машини скінченного стану) змінюється.

Зокрема, це може виглядати щось неясно (неперевірено):

   // 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частиною

У цьому випадку у вас є ще три можливості; і може змусити його працювати з тим самим підходом "3 петлі з кінцевою машиною стану", але він стає занадто брудним / потворним. У цьому випадку краща альтернатива, ймовірно, попередньо сканує масив, щоб визначити, чи є нулі та чи всі вони негативні чи всі позитивні; так що після попереднього сканування ви можете знати, що відповідь дорівнює нулю, або вибрати цикл, призначений для конкретної можливості.


1
Звідки береться нижня межа k на різницю індексу?
сіра борода

1
@greybeard: Це не так (я пропустив цю частину) - код потрібно змінити, щоб врахувати це.
Брендан

Навіщо тобі потрібні дві нулі?
TrentP

@TrentP: Арг - ти маєш рацію. Одного нуля достатньо, щоб знати, що відповідь - це 0 або негативне число.
Брендан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.