Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions 50/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# 50. Pow(x, n)

https://leetcode.com/problems/powx-n/

sol1.pyを迷わずかけた。n<0のケースでエラーが出たが、絶対値を取ることで処理し、それ以外ではつまらなかった。冪乗と聞いてビット演算を用いることを思いついた。

実行時間は0ms

計算量は時間計算量 O(log(n)) 、空間計算量 O(1)


> 1/2 乗って要するにルートのことですよね。

https://discord.com/channels/1084280443945353267/1262688866326941718/1351739946360111126

https://github.com/hroc135/leetcode/pull/43/changes#diff-788d4e4d3d8fc9d0d4ea879b815deba3b2c21e39a44274fe799eaa496c5d2e43

組み込み関数のコードを読んでいる
Copy link
Copy Markdown

@hroc135 hroc135 Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 組み込み関数ではなく、標準ライブラリですね。少なくとも自分が良く扱う言語ではここの区別はできた方がいいと思いました!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

math.Powとありましたね。失礼しました。


pythonのpowのドキュメント

https://docs.python.org/ja/3/library/functions.html#pow

powがmodの引数を取れることは知らなかった

https://github.com/TORUS0818/leetcode/pull/47/changes
再帰で書く

ループがかければ再帰は簡単に書けるように思う。
再帰をはじめに思いついたときにループに直せるようにしておきたい。

sol2.pyを再帰でかいた。

「指数を半分にしつつ、底を毎回 x**2 に置き換える」やりかただとオーバーフローが生じたので、底を変えない形にした。

追記:
これは x`*`xとすれば解決する。`**`演算だとエラーを投げますが、*演算だとinfを返すためである。


> IEEE-754の内部ビットの数も覚えておくと、面接でよく分かっている風が醸せることがあります。
> exponent が8ビットと11ビットです。符号が1ビットで残りが23ビットと52ビットです。

https://github.com/hroc135/leetcode/pull/43#discussion_r2002298814

(-1)^{符号} * 1.{仮数} * 2**{指数-bias}
bias = 2^{指数部のbit} -1

符号/指数/仮数

単精度 (float) 32 bits 1 / 8 / 23
倍精度 (double) 64 bits 1 / 11 / 52

指数部の8と11だけ覚えれば良い

この辺りの知識、頭から抜けてしまっているので復習したい


追記:
helper 関数を用いる場合を追記
このhelper関数は末尾再帰最適化を行うことで真価が発揮される。
しかし、pythonではこれが行われないらしく、追加の処理が必要になる
(末尾再帰最適化が実装されていないこと)
https://docs.python.org/3.15/whatsnew/3.14.html#whatsnew314-tail-call

しかし、Pythonは末尾再帰最適化を実装しておらず、これにはTrampolineという手法を用いるのが良いらしい。
自分で実装してみる
https://note.com/_ikb_/n/nc67f3e541f20


Geminiや記事を参考に自分なりに言語化すると、
最初に末尾再帰関数が呼ばれるとfirstcall=Trueなのでwhile文に入る。
nonlocalを使用しているため、tail_recursiveのlocal変数は全ての呼び出しで共通である。
そこで次の関数が呼ばれるがすでにfirstcall=Falseになっているので、引数だけを更新してfuncを返し、すぐに終了する。
これが続いて引数を更新し続け、最後に値が返ってwhileを抜ける。

実際にスタックのメモリ使用量に差が出ることを確認できた。
なお、実行時間はやや遅くなる

functools.wrapper
関数のメタデータを新しい関数に引き継ぐ

https://docs.python.org/ja/3/library/functools.html#functools.update_wrapper
11 changes: 11 additions & 0 deletions 50/sol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
result = 1
shifted = abs(n)
current_pow = x
while shifted > 0:
if shifted & 1:
result *= current_pow
shifted >>= 1
current_pow *= current_pow
return result if n >= 0 else 1 / result
11 changes: 11 additions & 0 deletions 50/sol1_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
result = 1.0
exponent = abs(n)
current_pow = x
while exponent > 0:
if exponent & 1:
result *= current_pow
exponent >>= 1
current_pow *= current_pow
return result if n >= 0 else 1 / result
10 changes: 10 additions & 0 deletions 50/sol2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1.0
if n < 0:
return 1.0 / self.myPow(x, -n)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には return self.myPow(1.0/x, -n) の方が好みです。
x^n = (1/x)^(-n) と書く方が、x^n = 1 / (x^(-n)) より自然だと思ったからです

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そちらを自然だと考える理由はなるほどです。
しかし逆数をとった時に生じる誤差が累積されることも考えて今回はこのままにしておこうと思います。
こういう複数の選択肢を選べるようにしておきたいです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

オーバーフローを引き起こしているので、ここでは良い選択肢とは言えないかもしれません。

「指数を半分にしつつ、底を毎回 x**2 に置き換える」やりかただとオーバーフローが生じたので、底を変えない形にした

こちらについてですが、オーバーフローした時に、**演算だとエラーを投げますが、*演算だとinfを返すので結果的に治っているという形かと。数値的にはpow(x ** 2, n // 2)も、pow(x, n // 2) ** 2も同じですよね。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘の通りで、x*xを渡すようにしたらエラーはなくなりますね。

**演算だとエラーを投げますが、*演算だとinfを返す

これは知らなかったです。勉強になりました

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には、引数を書き換えるために関数を再帰的に呼ぶのは、ややパズルに感じます。引数を直接書き換えてしまったほうがシンプルに感じます。

if n < 0:
    x = 1.0 / x
    n = -n

趣味の範囲かもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、再帰的に呼ばなくても解決するのですね。気づきませんでした。
revisedのファイルで採用させていただきました。

half = self.myPow(x, n // 2)
if n % 2 == 1:
return half * half * x
return half * half
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x^n * y を計算するヘルパーを定義する方法もありますね

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これを用いると末尾最適化ができそうですが、Pythonでは末尾最適化が行われないので、Trampolineという手法があることを知り、これを使って実装してみました

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trampoline 知らなかったので勉強になりました!

11 changes: 11 additions & 0 deletions 50/sol2_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1.0
if n < 0:
x = 1.0 / x
n = -n
sub_pow = self.myPow(x, n // 2)
if n % 2 == 1:
return sub_pow * sub_pow * x
return sub_pow * sub_pow
55 changes: 55 additions & 0 deletions 50/sol3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from functools import wraps


def tail_recursive(func):
firstcall = True
params = ((), {})
result = func

@wraps(func)
def wrapper(*args, **kwd):
nonlocal firstcall, params, result
params = args, kwd
if firstcall:
firstcall = False
try:
result = func
while result is func:
result = func(*args, **kwd) # call fact
args, kwd = params
finally:
firstcall = True
return result
else:
return func

return wrapper


class Solution:
@tail_recursive
def myPowHelper(self, base: float, exp: int, acc: float) -> float:
if exp == 0:
return acc
if exp % 2 == 1:
return self.myPowHelper(base, exp - 1, acc * base)
return self.myPowHelper(base * base, exp // 2, acc)

def myPow(self, x: float, n: int) -> float:
if n >= 0:
return self.myPowHelper(x, n, 1.0)
return 1 / self.myPowHelper(x, -n, 1.0)


if __name__ == "__main__":
import sys

_saved_limit = sys.getrecursionlimit()

try:
sys.setrecursionlimit(10)
x, n = 1.0001, 2**31 - 1
sol = Solution()
sol.myPow(x, n)
finally:
sys.setrecursionlimit(_saved_limit)