Алгоритъм за списък с връзки за намиране на двойки, добавящи до 10

Можете ли да предложите алгоритъм, който намира всички двойки възли в списък с връзки, които се събират до 10. Измислих следното.

Алгоритъм: Сравнете всеки възел, започвайки с втория възел, като всеки възел започва от главния възел до предишния възел (предишен на текущия сравняван възел) и докладвайте всички такива двойки.

Мисля, че този алгоритъм трябва да работи, но със сигурност не е най-ефективният със сложност O(n2).

Може ли някой да намекне за решение, което е по-ефективно (може би отнема линейно време). Допълнителни или временни възли могат да се използват от такова решение.


person Ankur    schedule 03.11.2009    source източник
comment
Могат ли стойностите да бъдат цяло число или диапазонът им е ограничен?   -  person paxdiablo    schedule 03.11.2009
comment
Колко двойки има в този списък (1,8,1,2,9,9,5)? 2 = (1,9),(8,2)? 3 = (1,9),(8,2),(1,9)? Съществуването на 5 означава ли, че (5,5) е двойка?   -  person jmucchiello    schedule 03.11.2009
comment
Стойността на възела може също да съдържа стойности, по-големи от 10   -  person Ankur    schedule 03.11.2009
comment
@jmucchiello: мисля, че алгоритъмът трябва да прави такива разграничения. Така че двойките трябва да са: (1,9),(1,9),(8,2). Само ако има две петици в списъка, трябва да се брои (5,5)   -  person Ankur    schedule 03.11.2009


Отговори (4)


Ако обхватът им е ограничен (да речем между -100 и 100), е лесно.

Създайте масив quant[-100..100], след което просто преминете през свързания списък, като изпълните:

quant[value] = quant[value] + 1

След това следващият цикъл ще свърши работа.

for i = -100 to 100:
    j = 10 - i
        for k = 1 to quant[i] * quant[j]
            output i, " ", j

Дори ако обхватът им не е ограничен, можете да имате по-ефективен метод от това, което сте предложили, като първо сортирате стойностите и след това просто запазите броя, а не отделни стойности (същото като горното решение).

Това се постига чрез стартиране на два указателя, един в началото на списъка и един в края. Когато числата в тези указатели се съберат до 10, изведете ги и преместете крайния показалец надолу и началния показалец нагоре.

Когато са по-големи от 10, преместете крайния показалец надолу. Когато са по-малко, преместете началния показалец нагоре.

Това разчита на подредената природа. По-малко от 10 означава, че трябва да увеличите сумата (преместете началния показалец нагоре). По-голямо от 10 означава, че трябва да направите сумата по-малка (краен показалец надолу). Тъй като те не са дубликати в списъка (поради броя), равно на 10 означава, че премествате и двата указателя.

Спрете, когато указателите се разминат.

Има още един труден момент и това е, когато указателите са равни и сумата на стойността е 10 (това може да се случи само когато стойността е 5, очевидно).

Вие не извеждате броя на двойките въз основа на продукта, а по-скоро той се основава на произведението на стойността минус 1. Това е така, защото стойност 5 с брой 1 всъщност не дава сбор на 10 (тъй като има само една 5).

И така, за списъка:

2 3 1 3 5 7 10 -1 11

Вие получавате:

Index    a  b  c  d  e  f  g  h
Value   -1  1  2  3  5  7 10 11
Count    1  1  1  2  1  1  1  1
  • Започвате показалеца p1 от a и p2 от h. От -1 + 11 = 10 извеждате тези две числа (както по-горе, правите го N пъти, където N е произведението на преброяването). Това е едно копие на (-1,11). След това премествате p1 в b и p2 в g.
  • 1 + 10 > 10 така че оставете p1 на b, преместете p2 надолу на f.
  • 1 + 7 < 10 така че преместете p1 на c, оставете p2 на f.
  • 2 + 7 < 10 така че преместете p1 на d, оставете p2 на f.
  • 3 + 7 = 10, изведете две копия на (3,7), тъй като броят на d е 2, преместете p1 на e, p2 на e.
  • 5 + 5 = 10 но p1 = p2 така че произведението е 0 по 0 или 0. Не изведете нищо, преместете p1 на f, p2 на d.
  • Цикълът завършва от p1 > p2.

Следователно общият резултат беше:

(-1,11)
( 3, 7)
( 3, 7)

кое е вярно.

Ето малко тестов код. Ще забележите, че принудих 7 (средната точка) до конкретна стойност за тестване. Очевидно не бихте направили това.

#include <stdio.h>

#define SZSRC 30
#define SZSORTED 20
#define SUM 14

int main (void) {
    int i, s, e, prod;
    int srcData[SZSRC];
    int sortedVal[SZSORTED];
    int sortedCnt[SZSORTED];

    // Make some random data.

    srand (time (0));
    for (i = 0; i < SZSRC; i++) {
        srcData[i] = rand() % SZSORTED;
        printf ("srcData[%2d] = %5d\n", i, srcData[i]);
    }

    // Convert to value/size array.

    for (i = 0; i < SZSORTED; i++) {
        sortedVal[i] = i;
        sortedCnt[i] = 0;
    }
    for (i = 0; i < SZSRC; i++)
        sortedCnt[srcData[i]]++;

    // Force 7+7 to specific count for testing.

    sortedCnt[7] = 2;
    for (i = 0; i < SZSORTED; i++)
        if (sortedCnt[i] != 0)
            printf ("Sorted [%3d], count = %3d\n", i, sortedCnt[i]);

    // Start and end pointers.

    s = 0;
    e = SZSORTED - 1;

    // Loop until they overlap.

    while (s <= e) {
        // Equal to desired value?

        if (sortedVal[s] + sortedVal[e] == SUM) {
            // Get product (note special case at midpoint).

            prod = (s == e)
                ? (sortedCnt[s] - 1) * (sortedCnt[e] - 1)
                : sortedCnt[s] * sortedCnt[e];

            // Output the right count.

            for (i = 0; i < prod; i++)
                printf ("(%3d,%3d)\n", sortedVal[s], sortedVal[e]);

            // Move both pointers and continue.

            s++;
            e--;
            continue;
        }

        // Less than desired, move start pointer.

        if (sortedVal[s] + sortedVal[e] < SUM) {
            s++;
            continue;
        }

        // Greater than desired, move end pointer.

        e--;
    }

    return 0;
}

Ще видите, че кодът по-горе е изцяло O(n), тъй като не сортирам в тази версия, а само интелигентно използвам стойностите като индекси.

Ако минимумът е под нулата (или много висок до точката, в която би изразходвал твърде много памет), можете просто да използвате minVal, за да коригирате индексите (друго O(n) сканиране, за да намерите минималната стойност и след това просто използвайте i-minVal вместо това от i за индекси на масив).

И дори ако диапазонът от ниско до високо е твърде скъп за памет, можете да използвате разреден масив. Ще трябва да го сортирате, O(n log n), и да го потърсите за актуализиране на броя, също O(n log n), но това пак е по-добре от оригиналния O(n2). Причината, поради която двоичното търсене е O(n log n), е, че едно единично търсене би било O(log n), но трябва да го направите за всяка стойност.

И ето резултат от тестово изпълнение, което ви показва различните етапи на изчисление.

srcData[ 0] =    13
srcData[ 1] =    16
srcData[ 2] =     9
srcData[ 3] =    14
srcData[ 4] =     0
srcData[ 5] =     8
srcData[ 6] =     9
srcData[ 7] =     8
srcData[ 8] =     5
srcData[ 9] =     9
srcData[10] =    12
srcData[11] =    18
srcData[12] =     3
srcData[13] =    14
srcData[14] =     7
srcData[15] =    16
srcData[16] =    12
srcData[17] =     8
srcData[18] =    17
srcData[19] =    11
srcData[20] =    13
srcData[21] =     3
srcData[22] =    16
srcData[23] =     9
srcData[24] =    10
srcData[25] =     3
srcData[26] =    16
srcData[27] =     9
srcData[28] =    13
srcData[29] =     5
Sorted [  0], count =   1
Sorted [  3], count =   3
Sorted [  5], count =   2
Sorted [  7], count =   2
Sorted [  8], count =   3
Sorted [  9], count =   5
Sorted [ 10], count =   1
Sorted [ 11], count =   1
Sorted [ 12], count =   2
Sorted [ 13], count =   3
Sorted [ 14], count =   2
Sorted [ 16], count =   4
Sorted [ 17], count =   1
Sorted [ 18], count =   1
(  0, 14)
(  0, 14)
(  3, 11)
(  3, 11)
(  3, 11)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  5,  9)
(  7,  7)

person paxdiablo    schedule 03.11.2009
comment
Бихте ли разяснили по-подробно втората част, можете просто да стартирате два индекса, един от началото и един от края, търсейки тези, които дават 10. - person Ankur; 03.11.2009
comment
Това решение има ли линейна сложност O(n)? - person Ankur; 05.11.2009
comment
Да, като цяло можете да разберете по липсата на вложени цикли. Решението, което дадох, беше O(n), защото разпределя брояч за всички възможни стойности. Ако искате да спестите памет, ще трябва да сортирате и търсите, което ще направи O(n log n), но представеното решение определено е O(n) времева сложност. - person paxdiablo; 05.11.2009
comment
Като настрана, понякога можете да замените сложността на съхранението за времева сложност в зависимост от това дали искате ослепителна скорост или нисък отпечатък на паметта. Казвам понякога, защото цената на агресивен във времето алгоритъм може да е повече място за съхранение, отколкото можете да поберете на планетата :-) - person paxdiablo; 05.11.2009

Създайте хеш набор (HashSet в Java) (може да използва разреден масив, ако вашите числа са добре ограничени, т.е. знаете, че попадат в +/- 100)

За всеки възел първо проверете дали 10-n е в комплекта. Ако е така, вие сте намерили чифт. Така или иначе, след това добавете n към набора и продължете.

Така например имате 1 - 6 - 3 - 4 - 9

1 - 9 ли са в комплекта? не

6 - 4? No.

3 - 7? No.

4 - 6? Мда! Печат (6,4)

9 - 1? Мда! Печат (9,1)

person Steven Schlansker    schedule 03.11.2009

Това е задача за сумата на мини подмножество, която е NP пълна.

person David    schedule 03.11.2009

Ако трябва първо да сортирате набора, това ще елиминира двойките числа, които трябва да бъдат оценени.

person David    schedule 04.11.2009
comment
Да, по-добър алгоритъм. Бихте ли казали, че това решение има линейна сложност? - person Ankur; 05.11.2009
comment
Мисля, че най-лошият случай ще бъде O(n^2). Трябва да помисля за това. - person David; 05.11.2009