Когда я был еще студентом Queens College, я собирался изучать алгоритмы компьютерных наук. Я столкнулся с этим конкретным вопросом, где меня просили вычислить число, возведенное в степень за время O (log N), и я был сбит с толку, потому что не мог понять, как это возможно.
Первое, что я сделал, - решил решить эту проблему так, как это было для меня понятно, и посмотреть, смогу ли я улучшить ее. Лучше иметь решение, чем в конце концов его не решать. Допустим, мы хотим вычислить 2⁵, тогда все это означает, что мне нужно умножить 2 * 2 * 2… пять раз.
function calculateExponent(base, exponent) { // 2,5 let result = 1; let absExponent = Math.abs(exponent); for (let i = 1; i <= absExponent; ++i) { // 1,2,3,4,5 result *= base; // 2,4,8,16,32 } return exponent < 0 ? 1 / result : result; } }
Пока этот код работает. Он выполняется только за время O (N), где N - показатель степени. Поскольку большинство решений O (log N) можно решить с помощью метода «разделяй и властвуй», я должен попробовать применить его к этой проблеме. Разделить и победить, по сути, означает разделить проблему на две и решить второстепенные проблемы и продолжать делить эти второстепенные проблемы на две, пока вы не сможете это сделать. Например, если мы хотим вычислить, что это 2⁸, мы можем разделить эту задачу на две, и тогда она будет в результате на 2⁴ и 2⁴, а затем снова разделить эту проблему на две, 2² и 2², пока мы в конечном итоге не дойдем до 1.
2^8 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 = 256 2^4 * 2^4 = 2^8 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 = 256 2^4 = 2 * 2 * 2 * 2 = 16 2^2 * 2^2 = 2^4 = 2 * 2 * 2 * 2 = 16 2^2 = 2 * 2 = 4 2^1 * 2^1 = 2^2 = 2 * 2 = 4 2^1 = 2 = 2
В приведенном выше фрагменте мы можем решить для 2⁸, зная только 2¹, 2² и 2⁴, поскольку 2¹ * 2¹ = 2² и 2² * 2² = 2⁴ и 2⁴ * 2⁴ = 2⁸. Это намного лучше, чем умножение 2 * 2… 8 раз. В математике это называется «степенным законом». Если два числа умножаются друг на друга с одинаковым основанием, мы можем сложить показатели. Применяя этот закон, мы можем увидеть, как «разделяй и властвуй» может помочь нам в решении этой проблемы, чтобы получить время O (log N).
Что делать, если показатель нечетный? Если показатель имеет нечетную степень, тогда мы все равно можем использовать «разделяй и властвуй», но тогда мы также должны умножить подзадачи на основание. Например, 2⁷ все еще можно разделить на 2, чтобы получить 2³ * 2³, и все, что нам нужно сделать, это умножить этот результат на основание, потому что 2³ * 2³ * 2¹ = 2⁷.
2^7 = 2 * 2 * 2 * 2 * 2 * 2 * 2 = 128 2^3 * 2^3 * 2^1 = 2^7 = 2 * 2 * 2 * 2 * 2 * 2 * 2 = 128 2^3 = 2 * 2 * 2 = 8 2^1 * 2^1 * 2^1 = 2^3 = 2 * 2 * 2 = 8 2^1 = 2 = 2
Видно, что большинство проблем «разделяй и властвуй» решаются с помощью рекурсии. Я придумал следующее рекурсивное решение.
function calculateExponent(base, exponent) { if (exponent < 0) {In this // exponent is negative return 1 / calculateExponent(base, exponent * -1); } if (exponent === 0) { // base case return 1; } let result = calculateExponent(base,Math.floor(exponent / 2)); return exponent % 2 === 0 ? result * result : result * result base; }
Пример: 2⁹
a) calculateExponent(2,9) | b) calculateExponent(2,4) | c) calculateExponent(2,2) | d) calculateExponent(2,1) | e) calculateExponent(2,0) | f) return 1; // 1, (2,0) | g) return 1 * 1 * 2; // 2, (2,1) | h) return 2 * 2; // 4, (2,2) | i) return 4 * 4; // 16, (2,4) | j) return 16 * 16 * 2; // 512, (2,9)
Существует также нерекурсивное решение со временем выполнения O (log N), и я настоятельно рекомендую вам решить его таким образом. Поэтому в следующий раз, когда вы столкнетесь с решением, требующим времени O (log N), всегда проверяйте, можно ли применить «разделяй и властвуй».