森田賢二のleetCodeの使い方、解説、解答を毎日更新しているキャラクター

[leetcode] 50. Pow(x, n)の「二分累乗法の効率化」はなぜ速いのか

[leetcode] 50. Pow(x, n)の「二分累乗法の効率化」はなぜ速いのか

const myPow = function(x, n) {
    function binaryExp(x, n){

        if(n === 0) return 1;
        if(n < 0) return 1 / binaryExp(x, -1 * n);

        if(n % 2 === 1){
            return x * binaryExp(x * x, (n - 1) / 2);
        } else {
            return binaryExp(x * x, n / 2);
        }
    }
    return binaryExp(x, n)
};

このmyPow関数は、数値xをn乗した結果を効率的に計算するためのものです。これは「二分累乗法」という手法を使っています。

基本ケース:

nが0の場合、どんな数でも0乗は1なので、1を返します。

nが負の場合、1 / binaryExp(x, -n)で計算します。これは正の指数で計算して、その結果を逆数にするためです。

再帰的な計算:

nが奇数なら、x binaryExp(x x, (n - 1) / 2)で計算します。xを一度掛けてから、nを半分にします。
nが偶数なら、binaryExp(x * x, n / 2)で計算します。nを半分にして計算を続けます。
例:
myPow(2, 10)の場合、結果は1024になります。このように、指数が大きくても高速に計算できます。

nが負の場合、1 / binaryExp(x, -n)で計算します。これは正の指数で計算して、その結果を逆数にするため?

nが負の場合、例えばx^-3のようなケースを考えてみましょう。

例としてmyPow(2, -3)を考えると:

計算手順:

nが負なので、nを正にして計算します。この場合、n = -3が正の3に変わります。
まず、2^3を計算します。2^3 = 8ですね。
次に、その結果を逆数にします。1 / 8 = 0.125になります。
つまり、2^-3という計算は、「まず2を正の指数で計算し、その結果を逆数にする」という流れで行われます。

なぜこうするのか?

負の指数は数学的に「逆数を取る」という意味を持ちます。x^-nは1 / x^nと同じです。例えば2^-3は、1 / 2^3、すなわち1 / 8です。

このように、まず正の指数で普通に計算してから、その結果の逆数を取ることで、負の指数も正しく計算できます。

なぜこの関数が速いのか

このmyPow関数が速い理由は、「二分累乗法」(Exponentiation by Squaring)を使っているからです。この方法では、計算の回数を大幅に減らすことができます。

通常の計算方法との違い

通常、x^nを計算するには、xをn回掛け算します。例えば、2^10を計算する場合、2 2 2 ... 2を10回行います。これは大きな指数では非常に多くの掛け算が必要になります。

二分累乗法の効率化
二分累乗法では、以下のように計算を分割して進めます:

nが偶数の場合:x^n = (x^2)^(n/2)

nが奇数の場合:x^n = x * (x^2)^((n-1)/2)

この方法では、毎回指数を半分にして計算するため、掛け算の回数が大幅に減ります。

具体例:2^10の場合

2^10は偶数なので、(2^2)^5、つまり4^5に変換します。
4^5は奇数なので、4 (4^2)^2に変換し、次に16^2を計算します。
最終的に16^2 = 256なので、4
256 = 1024という結果が得られます。
これにより、10回の掛け算をする代わりに、わずか4回の掛け算で済むようになります。指数が大きくなるほど、この効率の差はさらに大きくなります。