Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Алгоритм нахождения суммы степеней с уменьшающим основанием

#c_sharp #алгоритм #математика


Задача

Решаю задачу на Codewars: найти для следующей формулы n^3 + (n-1)^3 + ... + 1^3 =
m число n. Число m известно и передается в функцию FindNb. Если для переданного m не
существует n, то ответ: -1. Например:

FindNb(1071225) --> 45

FindNb(91716553919377) --> -1



Мое решение

Вот что сделал я:

public static long FindNb(long m)
{            
    long sum = 0;
    var nResult = 0;

    for (var i = 1; sum < m; i++)
    {
        sum = 0;
        nResult = i;
        for (var nMaybe = i; nMaybe > 0; nMaybe--)
        {
            sum += (long)Math.Pow(nMaybe, 3.0);
        }
    }
    if (sum != m)
    {
        nResult = -1;
    }

    return nResult;
}




Вопрос

Мое решение работает, но сервис требует оптимизировать код (слишком долго выполняется).
Как это сделать?


сложность моего алгоритма получилась квадратичная (N^2). Не знаю как
уменьшить ее.
также думал может приведение ...(long)Math.Pow... замедляет код, но
поиск оптимизации по этой части тоже ничего не дал.

    


Ответы

Ответ 1



А если попробовать такой вариант, без вложенных циклов: public static long FindNb1(long m) { var i = 0; long sum = 0; while (sum < m) { i++; sum = sum + (long)Math.Pow(i, 3.0); } if(sum != m) return -1; return i; } Дело вот в чём: счёт идёт обратно, от единицы: при n = 1 мы имеем формулу 1^3 при n = 2 мы имеем формулу 2^3 + 1^3 при n = 3 мы имеем формулу 3^3 + 2^3 + 1^3 и так далее. Накапливаем результат и на каждой итерации смотрим, не проскочили ли мы наш "эталон" в размере m. Если совпали с эталоном -- выводим счётчик цикла, если нет ("перескочили лишку") -- выводим -1. У меня Stopwatch показывает на 100 повторах кода ускорение на порядок (исходный вариант, оптмизированный): 16386 1309

Ответ 2



Реализация идеи из ответа @Harry с формулой S = [n(n+1)/2]^2 и квадратным корнем: public static long FindNb(long m) { var q = GetSqrtOrMinusOne(m); if (q == -1) return -1; // n(n+1)/2 = q <=> 4n^2 + 4n = 8q <=> 4n^2 + 4n + 1 = 8q + 1 <=> (2n + 1)^2 = 8q + 1 var r = GetSqrtOrMinusOne(8 * q + 1); // r = 2n + 1 if (r == -1) return -1; // 8q + 1 нечётно => r тоже нечётно, проверка не нужна return (r - 1) / 2; } static long GetSqrtOrMinusOne(long original) { // для чисел в диапазоне long точности хватает, так что можно считать, что // ошибка не превосходит 0.5 => используем Math.Round. var q = (int)Math.Round(Math.Sqrt(original)); return (q * q == original) ? q : -1; } Без циклов, O(1).

Ответ 3



Эта сумма равна (n*(n+1))^2/4. Оцениваем, зная m, величину n как корень четвертной степени из 4m - с избытком. Дальше идем вниз, пока не получим совпадение или значение, явно меньшее m. На C# не умею, на C++ long long FindNb(long long m) { long long n = sqrt(sqrt(4*m))+1; for(;;--n) { long long sum = n*n*(n+1)*(n+1)/4; if (sum == m) return n; if (sum < m) return -1; } } Можно решать явно - тут нужно просто аккуратненько с корнями работать... Просто функции sqrt() я бы не очень доверял. Как минимум, найдя - перепроверить... Дополнительная оптимизация - если m не точный квадрат - заведомо возвращаем -1.

Ответ 4



Если не использовать вариант (n * (n + 1))^2 / 4, то можно хотя бы не пересчитывать сумму для каждого i, а пользоваться суммой, полученной для i - 1: public static long FindNb(long m) { long sum = 0; for (var i = 1; sum < m; i++) { sum += (long)Math.Pow(i, 3.0); if (sum == m) return i; } return -1; } Если же вместо Math.Pow(i, 3.0) использовать просто (long)(i * i) * i или, если i гарантированно не будет превышать 1290 (что даёт сумму в 693 миллиарда), просто i * i * i (по предложению @rdorn), то работать будет ещё на порядок быстрее. public static long FindNb(long m) { long sum = 0; for (var i = 1; sum < m; i++) { sum += (long)(i * i) * i; if (sum == m) return i; } return -1; } Время работы для m = 1071225 при 100 тысячах итераций: Первоначальный вариант: 5110мс Math.Pow + сумма: 227мс i * i * i + сумма: 19мс Код @VladD: 10мс Время работы для m равного 100 миллиардам при 100 тысячах итераций: Первоначальный вариант: >60000мс Math.Pow + сумма: 3950мс i * i * i + сумма: 255мс Код @VladD: 4мс

Комментариев нет:

Отправить комментарий