3-сумный альтернативный подход - PullRequest
0 голосов
/ 09 июня 2018

Я попробовал альтернативный подход к проблеме 3sum : с помощью массива найти все триплеты, которые суммируют до заданного числа.

В основном подход такой: сортировка массива.Как только пара элементов (скажем, A [i] и A [j]) выбрана, для третьего элемента выполняется двоичный поиск [с использованием функции equal_range ].Индекс, следующий за последним из соответствующих элементов, сохраняется в переменной 'c'.Так как A [j + 1]> A [j], мы должны искать только до и исключая индекс c (так как числа в индексе c и далее определенно будут суммироваться больше, чем целевая сумма).Для случая j = i + 1 мы сохраняем индекс конца как 'd' и делаем c = d.Для следующего значения i, когда j = i + 1, нам нужно искать только до и исключая индекс d.

Реализация C ++:

int sum3(vector<int>& A,int sum)
{
    int count=0, n=A.size();
    sort(A.begin(),A.end());
    int c=n, d=n;  //initialize c and d to array length
    pair < vector<int>::iterator, vector<int>::iterator > p;
    for (int i=0; i<n-2; i++)
    {
        for (int j=i+1; j<n-1; j++)
        {
            if(j == i+1)
            {
                p=equal_range (A.begin()+j+1, A.begin()+d, sum-A[i]-A[j]);
                d = p.second - A.begin();
                if(d==n+1) d--;
                c=d;
            }
            else
            {
                p=equal_range (A.begin()+j+1, A.begin()+c, sum-A[i]-A[j]);
                c = p.second - A.begin();
                if(c==n+1) c--;
            }
            count += p.second-p.first;
            for (auto it=p.first; it != p.second; ++it) 
                cout<<A[i]<<' '<<A[j]<<' '<<*it<<'\n';
        }
    }
    return count;
}

int main()      //driver function for testing
{
    vector <int> A = {4,3,2,6,4,3,2,6,4,5,7,3,4,6,2,3,4,5};
    int sum = 17;
    cout << sum3(A,sum) << endl;
    return 0;
}

Я не могу определить время верхней границы, необходимое для этого алгоритма.Я понимаю, что наихудший сценарий будет, когда целевая сумма недостижимо велика.

Мои вычисления дают что-то вроде:

Для i = 0, нет.бинарных поисков: lg (n-2) + lg (n-3) + ... + lg (1)

для i = 1, lg (n-3) + lg (n-4)+ ... + lg (1)

...

...

...

Для i = n-3, lg(1)

Итак, lg ((n-2)!) + Lg ((n-3)!) + ... + lg (1!) = Lg (1 ^ n * 2 ^(n-1) 3 ^ (n-2) ... * (n-1) ^ 2 * n ^ 1)

Но как вывести оценку O (n)из этого выражения?

Ответы [ 3 ]

0 голосов
/ 09 июня 2018

В дополнение к хорошему ответу Джеймса я хотел бы отметить, что на самом деле это может пойти до O (n^3) в худшем случае, потому что вы запускаете 3 вложенных цикла.Рассмотрим случай

{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}

и требуемая сумма 3.

0 голосов
/ 09 июня 2018

1. Для вашего анализа учтите, что log(1) + log(2) + ... + log(k) = Theta(k log(k)).Действительно, верхняя половина этой суммы равна log (k / 2) + log (k / 2 + 1) + ... + log (k), так что она равна по крайней мере log (k / 2) * k / 2,которая асимптотически совпадает с log (k) * k уже.Точно так же мы можем сделать вывод, что

log(n-1) + log(n-2) + log(n-3) + ... + log(1) +  // Theta((n-1) log(n-1))
           log(n-2) + log(n-3) + ... + log(1) +  // Theta((n-2) log(n-2))
                      log(n-3) + ... + log(1) +  // Theta((n-3) log(n-3))
                                 ... +
                                       log(1) = Theta(n^2 log(n))

Действительно, если мы рассмотрим логарифмы, которые по крайней мере логарифмируют (n / 2), то это полутреугольник (то есть ~ 1/2) верхнего левого квадранта(то есть ~ n ^ 2/4) из вышеприведенной суммы, поэтому есть тэта (n ^ 2/8) таких терминов.

2. Как отметил Сатвик в другом ответе,ваш цикл вывода может выполнить до тета (n ^ 3) шагов, если само количество выходов равно Theta (n ^ 3), то есть когда они все равны.

3. Имеется O (n ^ 2) решений задачи с 3 суммами, которые поэтому асимптотически быстрее этой.

0 голосов
/ 09 июня 2018

При вычислении сложности я начну со справочной таблицы Big-O .Я использую этот лист для классификации небольших частей кода, чтобы получить их производительность во время выполнения.

Например, если бы у меня был простой цикл, это было бы O(n).BinSearch (согласно шпаргалке) имеет значение O(log(n)) и т. Д.

Далее я использую Свойства записи Big-O , чтобы объединить меньшие кусочки вместе.

Так, например, если бы у меня было два независимых друг от друга цикла, это было бы O(n) + O(n) или O(2n) => O(n).Если бы одна из моих петель была внутри другой, я бы умножил их.Итак, g( f(x) ) превращается в O(n^2).

Теперь я знаю, что вы говорите: «Эй, подожди, я изменяю верхнюю и нижнюю границы внутреннего цикла», но я не думаю, чтоэто действительно имеет значение ... вот пример университетского уровня .

Так что мой подсчет времени работы салфетки составляет O(n^2) * O(Log(n)) или O(n^2 Log(n)).

Но это не обязательно так.Я мог бы сделать что-то ужасно неправильно.Так что мой следующий шаг - начать график времени выполнения вашего наихудшего возможного случая.Установите сумму до невероятно большого значения и генерируйте все большие и большие массивы.Вы можете избежать целочисленного переполнения, используя много-много повторяющихся меньших чисел.

Кроме того, сравните его с квадратическим решением 3Sum .Это известное O(n^2) решение.Обязательно сравните худшие случаи или хотя бы один и тот же массив в обоих случаях.Выполните оба синхронизированных теста одновременно, чтобы вы могли почувствовать, что быстрее, пока вы эмпирически тестируете среду выполнения.

Выпуск сборок, оптимизированных для скорости.

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