-
Notifications
You must be signed in to change notification settings - Fork 0
50. Pow(x, n) #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
50. Pow(x, n) #43
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
|
||
| 組み込み関数のコードを読んでいる | ||
|
|
||
| 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 | ||
| 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 |
| 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 |
| 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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 個人的には
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そちらを自然だと考える理由はなるほどです。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. オーバーフローを引き起こしているので、ここでは良い選択肢とは言えないかもしれません。
こちらについてですが、オーバーフローした時に、
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ご指摘の通りで、x*xを渡すようにしたらエラーはなくなりますね。
これは知らなかったです。勉強になりました There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 個人的には、引数を書き換えるために関数を再帰的に呼ぶのは、ややパズルに感じます。引数を直接書き換えてしまったほうがシンプルに感じます。 if n < 0:
x = 1.0 / x
n = -n趣味の範囲かもしれません。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なるほど、再帰的に呼ばなくても解決するのですね。気づきませんでした。 |
||
| half = self.myPow(x, n // 2) | ||
| if n % 2 == 1: | ||
| return half * half * x | ||
| return half * half | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. x^n * y を計算するヘルパーを定義する方法もありますね
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これを用いると末尾最適化ができそうですが、Pythonでは末尾最適化が行われないので、Trampolineという手法があることを知り、これを使って実装してみました There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. trampoline 知らなかったので勉強になりました! |
||
| 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 |
| 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) |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: 組み込み関数ではなく、標準ライブラリですね。少なくとも自分が良く扱う言語ではここの区別はできた方がいいと思いました!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
math.Powとありましたね。失礼しました。